Compare commits

..
Author SHA1 Message Date
Maciej Szeszko b0fe00d3db Fix Git Codex Review invocation 2026-05-12 17:30:15 -07:00
632 changed files with 16108 additions and 121637 deletions

No files matched your search

@@ -0,0 +1,9 @@
name: install-jdk8-on-macos
runs:
using: composite
steps:
- name: Install JDK 8 on macos
run: |-
HOMEBREW_NO_AUTO_UPDATE=1 brew tap bell-sw/liberica
HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask liberica-jdk8
shell: bash
+5 -18
View File
@@ -2,13 +2,6 @@ name: pre-steps
runs:
using: composite
steps:
- name: Dump ulimits for diagnostics
run: |
echo "=== ulimits (soft) ==="
ulimit -a -S || true
echo "=== ulimits (hard) ==="
ulimit -a -H || true
shell: bash
- name: Install lld linker for faster builds
run: apt-get update -y && apt-get install -y lld 2>/dev/null || true
shell: bash
@@ -20,15 +13,9 @@ runs:
echo "GTEST_COLOR=1" >> "$GITHUB_ENV"
echo "CTEST_OUTPUT_ON_FAILURE=1" >> "$GITHUB_ENV"
echo "CTEST_TEST_TIMEOUT=300" >> "$GITHUB_ENV"
# These third-party sources are mirrored on the "third-party-deps" GitHub
# release. To add one, upload the asset here (intentionally hidden):
# https://github.com/facebook/rocksdb/releases/edit/third-party-deps
# Note how file names are generated below (vs. from the original source)
# before uploading. Removing or modifying files will break builds for
# older releases/revisions (not found or checksum mismatch).
echo "ZLIB_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/zlib-" >> "$GITHUB_ENV"
echo "BZIP2_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/bzip2-" >> "$GITHUB_ENV"
echo "SNAPPY_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/snappy-" >> "$GITHUB_ENV"
echo "LZ4_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/lz4-" >> "$GITHUB_ENV"
echo "ZSTD_DOWNLOAD_PREFIX=https://github.com/facebook/rocksdb/releases/download/third-party-deps/zstd-" >> "$GITHUB_ENV"
echo "ZLIB_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/zlib" >> "$GITHUB_ENV"
echo "BZIP2_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/bzip2" >> "$GITHUB_ENV"
echo "SNAPPY_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/snappy" >> "$GITHUB_ENV"
echo "LZ4_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/lz4" >> "$GITHUB_ENV"
echo "ZSTD_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/zstd" >> "$GITHUB_ENV"
shell: bash
@@ -1,17 +0,0 @@
name: setup-jdk-on-macos
runs:
using: composite
steps:
- name: Set up JDK on macos
run: |-
set -euo pipefail
JAVA_HOME="$(/usr/libexec/java_home -v 21)"
echo "JAVA_HOME=${JAVA_HOME}" >> "$GITHUB_ENV"
echo "JAVAC_ARGS=--release 8" >> "$GITHUB_ENV"
echo "${JAVA_HOME}/bin" >> "$GITHUB_PATH"
echo "Using JDK at ${JAVA_HOME}"
"${JAVA_HOME}/bin/java" -version
"${JAVA_HOME}/bin/javac" -version
shell: bash
+8 -57
View File
@@ -3,7 +3,7 @@ inputs:
suite-run:
description: Comma-separated list of test suites to run (empty to skip C++ tests)
required: false
default: arena_test,db_basic_test,db_test,db_etc2_test,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test
default: arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test
run-java:
description: Whether to run Java tests
required: false
@@ -11,49 +11,8 @@ inputs:
runs:
using: composite
steps:
- name: Detect Visual Studio generator
id: detect_vs
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$vswhere = Join-Path ([Environment]::GetFolderPath("ProgramFilesX86")) "Microsoft Visual Studio\Installer\vswhere.exe"
if (!(Test-Path $vswhere)) {
throw "vswhere.exe was not found at $vswhere"
}
$vsInstallationsJson = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json -utf8
$vsInstallations = @($vsInstallationsJson | ConvertFrom-Json)
if ($vsInstallations.Count -eq 0) {
throw "No Visual Studio instance with C++ tools was found."
}
$vsInstallation = $vsInstallations[0]
$vsMajor = [int]$vsInstallation.catalog.productLineVersion
if ($vsMajor -eq 0) {
$vsMajor = [int]($vsInstallation.installationVersion.Split(".")[0])
}
$generatorLine = cmake --help | Where-Object {
$_ -match "^\*?\s*Visual Studio $vsMajor\s+\d{4}\s*="
} | Select-Object -First 1
if (!$generatorLine) {
throw "CMake does not support a Visual Studio generator for VS major version $vsMajor."
}
$cmakeGenerator = ($generatorLine -replace "^\*?\s*", "" -replace "\s*=.*$", "").Trim()
$vsVersionRange = "[{0}.0,{1}.0)" -f $vsMajor, ($vsMajor + 1)
echo "Detected Visual Studio at $($vsInstallation.installationPath)"
echo "Using CMake generator: $cmakeGenerator"
echo "Using setup-msbuild vs-version: $vsVersionRange"
"CMAKE_GENERATOR=$cmakeGenerator" >> $env:GITHUB_ENV
"vs_version=$vsVersionRange" >> $env:GITHUB_OUTPUT
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
with:
vs-version: ${{ steps.detect_vs.outputs.vs_version }}
uses: microsoft/setup-msbuild@v1.3.1
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
@@ -73,23 +32,15 @@ runs:
CMAKE_HOME: C:/Program Files/CMake
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
SNAPPY_HOME: ${{ github.workspace }}/thirdparty/snappy-1.2.2
SNAPPY_INCLUDE: ${{ github.workspace }}/thirdparty/snappy-1.2.2;${{ github.workspace }}/thirdparty/snappy-1.2.2/build
SNAPPY_LIB_DEBUG: ${{ github.workspace }}/thirdparty/snappy-1.2.2/build/Debug/snappy.lib
run: |-
# NOTE: if ... Exit $LASTEXITCODE lines needed to exit and report failure
echo ===================== Install Dependencies =====================
if (!$env:JAVA_HOME -or !(Test-Path (Join-Path $env:JAVA_HOME "bin\javac.exe"))) {
$javac = Get-Command javac -ErrorAction SilentlyContinue
if (!$javac) {
Write-Error "javac was not found on PATH and JAVA_HOME is not set."
Exit 1
}
$env:JAVA_HOME = Split-Path (Split-Path $javac.Source -Parent) -Parent
}
echo "JAVA_HOME=$env:JAVA_HOME"
& "$env:JAVA_HOME\bin\java.exe" -version
& "$env:JAVA_HOME\bin\javac.exe" -version
choco install liberica8jdk -y
if(!$?) { Exit $LASTEXITCODE }
mkdir $Env:THIRDPARTY_HOME
cd $Env:THIRDPARTY_HOME
echo "Building Snappy dependency..."
@@ -102,11 +53,11 @@ runs:
cd build
& cmake -G "$Env:CMAKE_GENERATOR" .. -DSNAPPY_BUILD_TESTS=OFF -DSNAPPY_BUILD_BENCHMARKS=OFF
if(!$?) { Exit $LASTEXITCODE }
cmake --build . --config Debug --parallel 32 -- /p:Platform=x64
msbuild Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
echo ======================== Build RocksDB =========================
cd ${{ github.workspace }}
$env:Path = "$env:JAVA_HOME\bin;" + $env:Path
$env:Path = $env:JAVA_HOME + ";" + $env:Path
mkdir build
cd build
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DWIN_CI=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
@@ -114,7 +65,7 @@ runs:
cd ..
echo "Building with VS version: $Env:CMAKE_GENERATOR"
# use more parallel processes than the number of processes available, as most of the compile command would be cache hit
cmake --build build --config Debug --parallel 32 -- /p:LinkIncremental=false /p:Platform=x64
msbuild build/rocksdb.sln /m:32 /p:LinkIncremental=false -property:Configuration=Debug -property:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
echo ========================= Test RocksDB =========================
$suiteRun = "${{ inputs.suite-run }}"
+16 -33
View File
@@ -5,40 +5,23 @@
// return build({ icon, headerTitle, triggerLine, responseBody, footerLines
// });
// GitHub rejects issue comments longer than this many characters. The model
// response (responseBody) is the only unbounded input, so we trim it to fit
// while keeping the fixed scaffolding (header, trigger, footer) intact.
const MAX_COMMENT_LENGTH = 65536;
const TRUNCATION_NOTE =
'\n\n*[Truncated — full output in execution log artifact]*';
module.exports = function buildAiReviewComment(
{icon, headerTitle, triggerLine, responseBody, footerLines}) {
const assemble = (body) => [`## ${icon} ${headerTitle}`,
'',
triggerLine,
'',
'---',
'',
body,
'',
'---',
'',
'<details>',
'<summary>ℹ️ About this response</summary>',
'',
...footerLines,
'</details>',
return [
`## ${icon} ${headerTitle}`,
'',
triggerLine,
'',
'---',
'',
responseBody,
'',
'---',
'',
'<details>',
'<summary>ℹ️ About this response</summary>',
'',
...footerLines,
'</details>',
].join('\n');
const body = responseBody || '';
const comment = assemble(body);
if (comment.length <= MAX_COMMENT_LENGTH) {
return comment;
}
// Drop only as much of the body as needed to fit, then re-assemble so the
// footer/<details> block is preserved rather than cut off mid-comment.
const overflow = comment.length - MAX_COMMENT_LENGTH;
const keep = Math.max(0, body.length - overflow - TRUNCATION_NOTE.length);
return assemble(body.substring(0, keep) + TRUNCATION_NOTE);
};
+1 -1
View File
@@ -103,7 +103,7 @@ module.exports = function parseClaude({executionFile, conclusion, meta}) {
responseBody,
footerLines: [
'Generated by [Claude Code](https://github.com/anthropics/claude-code-action).',
'Review methodology: `claude_md/ci_review_prompt.md`',
'Review methodology: `claude_md/code_review.md`',
'',
'**Limitations:**',
'- Claude may miss context from files not in the diff',
+1 -1
View File
@@ -83,7 +83,7 @@ module.exports = function parseCodex(
responseBody,
footerLines: [
'Generated by Codex CLI.',
'Review methodology: `claude_md/ci_review_prompt.md`',
'Review methodology: `claude_md/code_review.md`',
'',
'**Limitations:**',
'- Codex may miss context from files not in the diff',
+14 -32
View File
@@ -40,7 +40,6 @@ jobs:
name: ${{ inputs.result_artifact_name }}
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path: _artifact
continue-on-error: true
- name: Post or update PR comment
@@ -53,18 +52,8 @@ jobs:
with:
script: |
const fs = require('fs');
const path = require('path');
const artifactDir = '_artifact';
const commentFile = path.join(
artifactDir,
process.env.COMMENT_FILE
);
const prNumberFile = path.join(artifactDir, 'pr_number.txt');
const triggerTypeFile = path.join(artifactDir, 'trigger_type.txt');
const headShaFile = path.join(artifactDir, 'head_sha.txt');
if (!fs.existsSync(commentFile) ||
!fs.existsSync(prNumberFile)) {
if (!fs.existsSync(process.env.COMMENT_FILE) ||
!fs.existsSync('pr_number.txt')) {
core.info('No review results found; skipping.');
return;
}
@@ -72,16 +61,16 @@ jobs:
const post = require('./.github/scripts/post-pr-comment.js');
const provider = process.env.PROVIDER;
const providerTitle = provider === 'codex' ? 'Codex' : 'Claude';
const body = fs.readFileSync(commentFile, 'utf8');
const body = fs.readFileSync(process.env.COMMENT_FILE, 'utf8');
const prNumber = parseInt(
fs.readFileSync(prNumberFile, 'utf8').trim(),
fs.readFileSync('pr_number.txt', 'utf8').trim(),
10
);
const trigger = fs.existsSync(triggerTypeFile) ?
fs.readFileSync(triggerTypeFile, 'utf8').trim() :
const trigger = fs.existsSync('trigger_type.txt') ?
fs.readFileSync('trigger_type.txt', 'utf8').trim() :
'auto';
const headSha = fs.existsSync(headShaFile) ?
fs.readFileSync(headShaFile, 'utf8').trim() :
const headSha = fs.existsSync('head_sha.txt') ?
fs.readFileSync('head_sha.txt', 'utf8').trim() :
'';
const shortSha = headSha ? headSha.substring(0, 7) : '';
const runId = process.env.RUN_ID;
@@ -130,16 +119,12 @@ jobs:
with:
script: |
const fs = require('fs');
const path = require('path');
const artifactDir = '_artifact';
const triggerTypeFile = path.join(artifactDir, 'trigger_type.txt');
const commentIdFile = path.join(artifactDir, 'comment_id.txt');
if (!fs.existsSync(triggerTypeFile)) return;
const trigger = fs.readFileSync(triggerTypeFile, 'utf8').trim();
if (!fs.existsSync('trigger_type.txt')) return;
const trigger = fs.readFileSync('trigger_type.txt', 'utf8').trim();
if (trigger !== 'manual') return;
if (!fs.existsSync(commentIdFile)) return;
if (!fs.existsSync('comment_id.txt')) return;
const commentId = parseInt(
fs.readFileSync(commentIdFile, 'utf8').trim(),
fs.readFileSync('comment_id.txt', 'utf8').trim(),
10
);
if (!commentId) return;
@@ -162,7 +147,6 @@ jobs:
name: ${{ inputs.result_artifact_name }}
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path: _artifact
continue-on-error: true
- name: React with failure
@@ -171,11 +155,9 @@ jobs:
with:
script: |
const fs = require('fs');
const path = require('path');
const commentIdFile = path.join('_artifact', 'comment_id.txt');
if (!fs.existsSync(commentIdFile)) return;
if (!fs.existsSync('comment_id.txt')) return;
const commentId = parseInt(
fs.readFileSync(commentIdFile, 'utf8').trim(),
fs.readFileSync('comment_id.txt', 'utf8').trim(),
10
);
if (!commentId) return;
+3 -11
View File
@@ -25,7 +25,6 @@ jobs:
name: clang-tidy-result
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path: _artifact
continue-on-error: true
- name: Post or update PR comment
@@ -34,19 +33,12 @@ jobs:
with:
script: |
const fs = require('fs');
if (!fs.existsSync('_artifact/clang-tidy-comment.md') ||
!fs.existsSync('_artifact/pr_number.txt')) {
if (!fs.existsSync('clang-tidy-comment.md') || !fs.existsSync('pr_number.txt')) {
core.info('No clang-tidy results found; skipping.');
return;
}
const body = fs.readFileSync(
'_artifact/clang-tidy-comment.md',
'utf8'
);
const prNumber = parseInt(
fs.readFileSync('_artifact/pr_number.txt', 'utf8').trim(),
10
);
const body = fs.readFileSync('clang-tidy-comment.md', 'utf8');
const prNumber = parseInt(fs.readFileSync('pr_number.txt', 'utf8').trim());
const post = require('./.github/scripts/post-pr-comment.js');
await post({
@@ -22,3 +22,4 @@ jobs:
provider: claude
result_artifact_name: claude-review-result
comment_file: claude-review-comment.md
secrets: inherit
@@ -22,3 +22,4 @@ jobs:
provider: codex
result_artifact_name: codex-review-result
comment_file: codex-review-comment.md
secrets: inherit
+8 -3
View File
@@ -7,9 +7,14 @@
name: Codex Code Review
on:
# Automatic PR reviews are temporarily disabled while Codex authentication is
# repaired. Manual review requests remain available through issue comments
# and workflow_dispatch.
workflow_run:
workflows: ["facebook/rocksdb/pr-jobs"]
types: [completed]
# The early pull_request_target path is limited to same-repo PRs by the job
# condition below. Fork PRs skip this path and rely on workflow_run instead.
pull_request_target:
types: [opened, reopened, synchronize]
issue_comment:
types: [created]
+4 -13
View File
@@ -113,10 +113,11 @@ jobs:
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-windows-msvc-avx2:
build-windows-vs2022-avx2:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: windows-8-core
runs-on: windows-2022
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_PORTABLE: AVX2
steps:
- uses: actions/checkout@v4.1.0
@@ -140,17 +141,7 @@ jobs:
- uses: "./.github/actions/pre-steps"
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
- run: sudo mount -o remount,size=16G /dev/shm
- name: Ensure swap space
run: |-
if sudo swapon --show=NAME --noheadings --raw | grep -Fxq /swapfile; then
echo "/swapfile is already active"
else
sudo dd bs=1048576 count=4096 if=/dev/zero of=/swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
fi
sudo swapon --show
- run: sudo dd bs=1048576 count=4096 if=/dev/zero of=/swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_atomic_flush
- run: rm -rf /dev/shm/rocksdb.*
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_multiops_wc_txn
+15 -68
View File
@@ -65,9 +65,7 @@ jobs:
pip install https://files.pythonhosted.org/packages/fb/ac/3c04772acc0257f5730e83adb542b2603c1a62d1315010ab593a980af404/clang_format-21.1.2-py2.py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
clang-format --version
- name: Download clang-format-diff.py
# Mirrored on the "third-party-deps" GitHub release; add/update via
# https://github.com/facebook/rocksdb/releases/edit/third-party-deps
run: wget --tries=3 --retry-on-host-error https://github.com/facebook/rocksdb/releases/download/third-party-deps/clang-format-diff.py
run: wget https://rocksdb-deps.s3.us-west-2.amazonaws.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
- name: Check format
run: VERBOSE_CHECK=1 make check-format
- name: Compare buckify output
@@ -316,53 +314,6 @@ jobs:
- run: CC=clang-21 CXX=clang++-21 USE_CLANG=1 make -j32 all microbench
- run: make clean
- run: CC=clang-21 CXX=clang++-21 USE_CLANG=1 DEBUG_LEVEL=0 make -j32 release
- name: Install clang-format-21 for C API gen checks
run: apt-get update && apt-get install -y clang-format-21
- name: Check C API codegen (link-complete, backward-compatible, up to date)
# Run the make target in STRICT mode so this core CI job HARD-FAILS rather
# than silently skipping if a prerequisite (clang++, clang-format, or the
# compat baseline ref) is ever missing. See `make check-c-api-gen`.
env:
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
# The container checkout is owned by a different user, so git refuses to
# operate on it ("dubious ownership") -- mark it safe for the `git fetch`
# and the `git show` the compat checker runs.
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global --add safe.directory '*'
# A PR must preserve the API of its merge target, while a push must
# preserve the API of the branch before the push. Comparing every event
# with main produces false removals when a release branch predates APIs
# added to main.
case "$GITHUB_EVENT_NAME" in
pull_request) api_compat_ref="$PR_BASE_SHA" ;;
push) api_compat_ref="$PUSH_BEFORE_SHA" ;;
*)
echo "ERROR: unsupported event for C API compatibility: $GITHUB_EVENT_NAME" >&2
exit 1
;;
esac
if [ -z "$api_compat_ref" ]; then
echo "ERROR: C API compatibility baseline is empty" >&2
exit 1
fi
zero_sha=0000000000000000000000000000000000000000
if [ "$api_compat_ref" = "$zero_sha" ]; then
# A newly created branch or tag has no prior ref state to preserve.
api_compat_ref=HEAD
else
git fetch --no-tags --depth=1 origin "$api_compat_ref"
api_compat_ref=FETCH_HEAD
fi
CXX=clang++-21 make check-c-api-gen \
CHECK_C_API_GEN_STRICT=1 \
API_COMPAT_REF="$api_compat_ref" \
CLANG_FORMAT_BINARY=clang-format-21
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
@@ -422,24 +373,14 @@ jobs:
crash_duration: 240
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb --ulimit nofile=1048576:1048576
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/setup-ccache"
with:
cache-key-prefix: mini-crashtest
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: |
ulimit -S -n $(ulimit -Hn) # Raise open files soft limit to hard limit
echo "Updated ulimit -Hn: $(ulimit -Hn) -Sn: $(ulimit -Sn)"
USE_COROUTINES=1 USE_FOLLY=1 LIB_MODE=static make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=${{ matrix.crash_duration }} --max_key=2500000' ${{ matrix.crash_test_target }}
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=${{ matrix.crash_duration }} --max_key=2500000' ${{ matrix.crash_test_target }}
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
@@ -581,8 +522,8 @@ jobs:
- uses: "./.github/actions/post-steps"
# ======================== Windows with Tests ======================= #
# NOTE: some windows jobs are in "nightly" to save resources
build-windows-msvc:
if: needs.config.outputs.only_job == 'build-windows-msvc' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
build-windows-vs2022:
if: needs.config.outputs.only_job == 'build-windows-vs2022' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: windows-8-core
strategy:
@@ -593,12 +534,13 @@ jobs:
suite_run: db_test
run_java: "false"
- test_shard: other
suite_run: arena_test,db_basic_test,db_etc2_test,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test
suite_run: arena_test,db_basic_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test
run_java: "false"
- test_shard: java
suite_run: ""
run_java: "true"
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_PORTABLE: 1
steps:
- uses: actions/checkout@v4.1.0
@@ -658,6 +600,7 @@ jobs:
needs: config
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
ROCKSDB_DISABLE_JEMALLOC: 1
steps:
- uses: actions/checkout@v4.1.0
@@ -669,7 +612,7 @@ jobs:
cache-key-prefix: macos-java
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/setup-jdk-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Set Java Environment
run: |-
@@ -685,6 +628,8 @@ jobs:
if: needs.config.outputs.only_job == 'build-macos-java-static' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
@@ -695,7 +640,7 @@ jobs:
cache-key-prefix: macos-java-static
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/setup-jdk-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Set Java Environment
run: |-
@@ -711,6 +656,8 @@ jobs:
if: needs.config.outputs.only_job == 'build-macos-java-static-universal' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: macos-15-xlarge
env:
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
steps:
- uses: actions/checkout@v4.1.0
- uses: maxim-lobanov/setup-xcode@v1.6.0
@@ -721,7 +668,7 @@ jobs:
cache-key-prefix: macos-java-static-universal
- uses: "./.github/actions/increase-max-open-files-on-macos"
- uses: "./.github/actions/install-gflags-on-macos"
- uses: "./.github/actions/setup-jdk-on-macos"
- uses: "./.github/actions/install-jdk8-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Set Java Environment
run: |-
+1 -5
View File
@@ -1,7 +1,5 @@
make_config.mk
rocksdb.pc
.build_signature
build_tools/__pycache__/
*.a
*.arc
@@ -54,7 +52,7 @@ GRTAGS
GTAGS
rocksdb_dump
rocksdb_undump
db_etc2_test
db_test2
trace_analyzer
block_cache_trace_analyzer
io_tracer_parser
@@ -90,8 +88,6 @@ fbcode/
fbcode
buckifier/*.pyc
buckifier/__pycache__
tools/__pycache__/
tools/c_api_gen/__pycache__/
.arcconfig
compile_commands.json
+7 -44
View File
@@ -35,7 +35,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/blob/blob_file_partition_manager.cc",
"db/blob/blob_file_reader.cc",
"db/blob/blob_garbage_meter.cc",
"db/blob/blob_gen2_format.cc",
"db/blob/blob_log_format.cc",
"db/blob/blob_log_sequential_reader.cc",
"db/blob/blob_log_writer.cc",
@@ -59,7 +58,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/compaction/sst_partitioner.cc",
"db/compaction/subcompaction_state.cc",
"db/convenience.cc",
"db/coro_db.cc",
"db/db_filesnapshot.cc",
"db/db_impl/compacted_db_impl.cc",
"db/db_impl/db_impl.cc",
@@ -96,7 +94,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/merge_operator.cc",
"db/multi_scan.cc",
"db/output_validator.cc",
"db/periodic_compaction_phaser.cc",
"db/periodic_task_scheduler.cc",
"db/range_del_aggregator.cc",
"db/range_tombstone_fragmenter.cc",
@@ -105,6 +102,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/snapshot_impl.cc",
"db/table_cache.cc",
"db/table_properties_collector.cc",
"db/transaction_log_impl.cc",
"db/trim_history_scheduler.cc",
"db/version_builder.cc",
"db/version_edit.cc",
@@ -112,9 +110,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"db/version_set.cc",
"db/version_util.cc",
"db/wal_edit.cc",
"db/wal_iterator_impl.cc",
"db/wal_manager.cc",
"db/wide/lazy_wide_columns.cc",
"db/wide/read_path_blob_resolver.cc",
"db/wide/wide_column_serialization.cc",
"db/wide/wide_columns.cc",
@@ -206,7 +202,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"table/block_based/block_cache.cc",
"table/block_based/block_prefetcher.cc",
"table/block_based/block_prefix_index.cc",
"table/block_based/builtin_index_factory.cc",
"table/block_based/data_block_footer.cc",
"table/block_based/data_block_hash_index.cc",
"table/block_based/filter_block_reader_common.cc",
@@ -272,7 +267,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"util/compression.cc",
"util/compression_context_cache.cc",
"util/concurrent_task_limiter_impl.cc",
"util/coro_stats_util.cc",
"util/crc32c.cc",
"util/crc32c_arm64.cc",
"util/data_structure.cc",
@@ -311,7 +305,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"utilities/compaction_filters.cc",
"utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
"utilities/convenience/info_log_finder.cc",
"utilities/copy_engine/copy_engine.cc",
"utilities/counted_fs.cc",
"utilities/debug.cc",
"utilities/env_mirror.cc",
@@ -385,21 +378,11 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
], deps=[
"//folly/container:f14_hash",
"//folly/coro:baton",
"//folly/coro:blocking_wait",
"//folly/coro:collect",
"//folly/coro:coroutine",
"//folly/coro:nothrow",
"//folly/coro:task",
"//folly/executors/thread_factory:named_thread_factory",
"//folly/executors:io_executor",
"//folly/executors:io_thread_pool_executor",
"//folly/io/async:async_base",
"//folly/io/async:event_base_manager",
"//folly/io/async:io_uring_backend",
"//folly/io/async:request_context",
"//folly/synchronization:distributed_mutex",
"//folly:executor",
], headers=glob(["**/*.h"]), link_whole=False, extra_test_libs=False)
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[], deps=[":rocksdb_lib"], headers=[], link_whole=True, extra_test_libs=False)
@@ -4615,12 +4598,6 @@ cpp_unittest_wrapper(name="bloom_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="builtin_index_factory_test",
srcs=["table/block_based/builtin_index_factory_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="cache_reservation_manager_test",
srcs=["cache/cache_reservation_manager_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -4765,12 +4742,6 @@ cpp_unittest_wrapper(name="configurable_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="copy_engine_test",
srcs=["utilities/copy_engine/copy_engine_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="corruption_test",
srcs=["db/corruption_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -4897,12 +4868,6 @@ cpp_unittest_wrapper(name="db_encryption_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_etc2_test",
srcs=["db/db_etc2_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_etc3_test",
srcs=["db/db_etc3_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -4957,12 +4922,6 @@ cpp_unittest_wrapper(name="db_kv_checksum_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_lazy_entity_test",
srcs=["db/wide/db_lazy_entity_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_log_iter_test",
srcs=["db/db_log_iter_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5065,6 +5024,12 @@ cpp_unittest_wrapper(name="db_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_test2",
srcs=["db/db_test2.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="db_universal_compaction_test",
srcs=["db/db_universal_compaction_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5830,5 +5795,3 @@ cpp_unittest_wrapper(name="write_unprepared_transaction_test",
export_file(name = "tools/db_crashtest.py")
export_file(name = "tools/fault_injection_log_parser.py")
+24 -167
View File
@@ -14,12 +14,6 @@ This document provides guidance for generating and reviewing code in the RocksDB
**Error Handling:** Ensure robust error handling throughout the codebase. Use RocksDB's `Status` type consistently, propagate errors appropriately, and avoid silently ignoring failures. Reviewers pay close attention to edge cases and failure modes.
**Refactoring traps:** At least in production code, avoid constructs that could allow existing code to unexpectedly, quietly change meaning.
Specifically:
* Avoid new defaulted parameters. This is the #1 trap on refactoring!
* Avoid static_cast, reinterpret_cast, and C-style casts; static_cast_with_check, up_cast, and lossless_cast from cast_util.h are preferred.
* Avoid declared type `auto` (`auto&`, `auto*` OK).
### Testing Philosophy
**Comprehensive Coverage:** Every change should include appropriate test coverage. This includes unit tests for isolated functionality, integration tests for component interactions, and stress tests for concurrency and performance validation. Reviewers will ask for additional tests if coverage is insufficient.
@@ -38,13 +32,13 @@ Specifically:
**Memory Copy:** Avoid unnecessary memory copies. Use move semantics, `std::string_view`, `Slice`, and pass-by-reference where appropriate. Be aware of implicit copies in STL containers and function returns. Prefer in-place operations over copy-and-modify patterns.
**CPU Cache Efficiency:** Design data structures and access patterns to be cache-friendly. Keep frequently accessed data together (data locality). Prefer sequential memory access over random access. Be mindful of cache line sizes (CACHE_LINE_SIZE, typically 64 bytes) and avoid false sharing in concurrent code (see CacheAlignedWrapper). Especially when adding new data members, be mindful of ordering within structs and classes to reduce unnecessary padding (special care with bool members) and avoid larger-than-necessary types (e.g. prefer OptSlice to std::optional<Slice>).
**CPU Cache Efficiency:** Design data structures and access patterns to be cache-friendly. Keep frequently accessed data together (data locality). Prefer sequential memory access over random access. Be mindful of cache line sizes (typically 64 bytes) and avoid false sharing in concurrent code. Consider struct packing and field ordering to improve cache utilization.
**Loop Optimization:** Look for opportunities to collapse nested loops, reduce loop overhead, and minimize branch mispredictions. Hoist invariant computations out of loops. Consider loop unrolling for tight inner loops. Batch operations when possible to amortize per-operation overhead.
**SIMD and Vectorization:** Leverage SIMD instructions (SSE, AVX) for data-parallel operations when appropriate. Structure data to enable auto-vectorization by the compiler. Consider explicit SIMD intrinsics for critical hot paths like checksum computation, encoding/decoding, and bulk data processing.
**Branch Prediction:** Minimize unpredictable branches in hot paths. Use `LIKELY`/`UNLIKELY` macros to hint branch prediction, e.g. for error cases and other rare or otherwise costly cases, but NOT for predicting popular configurations. Consider branchless alternatives for simple conditionals. Order switch cases and if-else chains by frequency.
**Branch Prediction:** Minimize unpredictable branches in hot paths. Use `LIKELY`/`UNLIKELY` macros to hint branch prediction. Consider branchless alternatives for simple conditionals. Order switch cases and if-else chains by frequency.
**Memory and Resource Management:** Be mindful of memory allocations, especially in hot paths. Use RAII patterns, smart pointers, and RocksDB's memory management utilities appropriately.
@@ -53,17 +47,15 @@ Specifically:
- **Cold path** (executed rarely, e.g., DB open, configuration parsing, error handling): Maintainability and clarity are more important. Prefer readable code over micro-optimizations. Complex optimizations here add maintenance burden with negligible performance benefit.
- **Warm path** (moderate frequency): Balance both concerns. Use profiling data to guide optimization decisions.
**Avoid Premature Optimization:** Performance is known critical in places like inner loops of compaction, read path and write path. Elsewhere, try to find a "holy trinity" solution of *correct, simple, and reasonably efficient*. More complex solutions are only appropriate when profiling/benchmarking data shows a measurable payoff.
**Avoid Premature Optimization:** While performance is critical, focus on correctness first, then optimize based on profiling data. However, be performance-aware from the start—choosing the right algorithm and data structure upfront is not premature optimization. Use the hot path analysis above to decide how much optimization effort is warranted.
### API Design and Compatibility
**Backwards Compatibility:** RocksDB maintains strong backwards compatibility guarantees. Breaking changes are rare and require extensive justification. When deprecating features, follow the project's deprecation policy (typically spanning multiple releases).
**Behavioral Compatibility:** Behavior changes are human judgment calls that must be intentional, well-documented (including release notes), and well researched for potential downstream production impact. Agents and reviewers must call out potential gaps, ambiguity, or adverse impacts in intentionality, documentation, and production impact. For an existing accepted input—including an undocumented, discouraged, or historically ignored value—the analysis should compare old and new observable behavior, including performance and resource impact, identify known direct callers and stored configurations, and add or update regression coverage as appropriate.
**API Consistency:** New APIs should be consistent with existing patterns. Use similar naming conventions, parameter ordering, and return types. Reviewers will suggest changes to improve consistency with the broader codebase.
**Documentation:** Public APIs must be thoroughly documented, without unnecessary embelishment nor dwelling on implementation details nor project planning. When non-obvious, include usage examples, parameter descriptions, known bugs or limitations, notes on thread safety, performance characteristics, and compatibility considerations. Re-read comments for ambiguous terminology and phrasing, such as ambiguously re-purposed programming jargon.
**Documentation:** Public APIs must be thoroughly documented. Include usage examples, parameter descriptions, and notes on thread safety, performance characteristics, and compatibility considerations.
---
@@ -81,7 +73,7 @@ The database core handles write-ahead logging (WAL), memtables, compaction, and
**Testing:** Database core changes require extensive testing, including unit tests, integration tests, and stress tests. Test with various configurations, compaction styles, and concurrent workloads.
### Public Headers / "Public API" (`include/rocksdb/`)
### Public Headers (`include`)
Public headers define RocksDB's API surface. Changes here have the highest compatibility impact.
@@ -157,26 +149,6 @@ Cache management is critical for RocksDB's performance.
When reviewing RocksDB code (or preparing code for review), use this checklist:
### Contract Boundaries
- [ ] Is each behavior owned by the right layer? High-level policy (for example,
"compaction wants this I/O mode") should live at the caller/policy layer, while
lower layers should expose generic mechanisms (for example, "open a fresh
reader", "skip shared cache insertion", or "use these FileOptions").
- [ ] Do comments and names describe local contracts rather than leaking a
specific caller's rationale into reusable APIs? Generic code should not need to
know about one current use case unless the API itself is intentionally
use-case-specific.
- [ ] Does each flag or parameter control one coherent behavior? If one boolean
starts implying ownership, cache policy, I/O mode, prefetching, and caller
identity, split it into explicit flags or an options struct.
- [ ] Could a future caller use this lower-level API without accidentally
inheriting assumptions from compaction, backup, user reads, or a particular
table format? If not, tighten the contract with assertions, clearer names, or
a narrower API.
- [ ] Are implementation details not being used as policy signals? Prefer an
explicit contract over inferring behavior from incidental fields such as file
options, cache handles, or current table-reader state.
### Correctness
- [ ] Does the change preserve database semantics (e.g., snapshot isolation, key ordering)?
- [ ] Are all error cases handled appropriately?
@@ -232,104 +204,18 @@ The following patterns emerged as frequent sources of review feedback:
10. **Platform Compatibility:** Ensure changes work correctly on all supported platforms (Linux, Windows, macOS) and with all supported compilers (GCC, Clang, MSVC).
11. **Contract Boundary Leaks:** When a change plumbs a new option or use-case
specific behavior through multiple subsystems, review the call chain for
contract leaks. Caller-specific rationale belongs at the call site or public API
documentation; reusable layers should expose precise, layer-local capabilities.
Watch especially for comments mentioning one caller in generic code, booleans
that silently bundle several behaviors, and downstream code inferring policy
from an implementation detail instead of an explicit option.
---
## Important tips
### Build system
* There are 3 build system. Make for git clones, BUCK (meta internal) for hg
clones, and CMake for some special cases.
* There are 3 build system. Make, CMake, BUCK(meta internal).
* When a new .cc file is added, update Makefile, CMakeLists.txt, src.mk, BUCK.
* Don't manually edit BUCK file, after updating src.mk, run
/usr/local/bin/python3 buckifier/buckify_rocksdb.py to update it
* Use make to build and run the test. CMake and BUCK are not used locally.
* Use `make dbg` command to build all of the unit test in debug mode.
* For -j in make command, use the number of CPU cores to decide it.
* When searching for references to something (a symbol, library, etc.), do not
restrict or truncate your search based on presumed relevance or scope. It is
important and time-saving to keep the repo reasonably consistent across
different build systems, programming languages, and even between
documentation and implementation.
### Avoiding mixed build modes with Make (use `AUTO_CLEAN=1`)
Object files are written to the same paths regardless of build flags, so
reusing objects from a prior build with different flags causes confusing
linker errors, etc. This problem is essentially avoidable by ALWAYS using
`AUTO_CLEAN=1 make -j<n> <something>` for manual make invocations. This
will automatically clean object files if the build parameters/flavor have
changed. The `build_tools/rockstest.sh` / `rocksptest.sh` helpers described
below set `AUTO_CLEAN=1` for you.
`AUTO_CLEAN=1` does not fix Make failures associated with stale .d files
referring to removed files. Resolve that manually or with `make clean`
without complaining to the user.
### Source checks
* Run `make check-sources` before committing. This catches non-ASCII
characters in source files and other source-level issues that CI will
reject. In particular, **do not use Unicode characters** (em dashes,
smart quotes, etc.) in comments or strings -- use ASCII equivalents
(`--` instead of em dash, `'` instead of smart quote, etc.).
### License headers
* Every new source file needs a license header. For a file that does **not**
carry an outside/third-party copyright, use the standard Meta dual-licensed
header (the dual-license designation is required -- a bare
"All Rights Reserved" copyright is not an acceptable open-source header):
```
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
```
Use a `#` comment prefix instead of `//` for shell, Python, and Makefile
fragments.
* Files derived from an external source (e.g. LevelDB) keep their original
upstream copyright line in addition to the header above.
### RTTI and dynamic_cast
* Production code and `db_stress` must build in **release mode
(`-fno-rtti`)**. Do not use `dynamic_cast` anywhere except unit tests.
Use `static_cast_with_check` from `util/cast_util.h` (validates with
`dynamic_cast` in debug builds, plain `static_cast` in release).
* Unit tests (`*_test.cc`) are built in debug mode with RTTI enabled.
### Cross-platform / portability
Local `make` only exercises Linux with GCC/Clang, but CI
(`.github/workflows/pr-jobs.yml` and `nightly.yml`) gates on a much wider
matrix, so portability breaks are invisible locally until CI fails. Code must
build (and where noted, run tests) across:
| Axis | Must support |
|------|--------------|
| OS | Linux (x86_64 + ARM), macOS, Windows |
| Architecture | Little-endian and big-endian targets, including Linux on s390x |
| Compiler | GCC, Clang (libstdc++ **and** libc++), AppleClang, **MSVC (VS2022)**, MinGW (Linux cross-compile, build-only, no gflags) |
| Build system | Make, CMake, and BUCK (internal) -- keep all in sync (see "Build system" above) |
| Config | release (`-fno-rtti`), `ASSERT_STATUS_CHECKED`, ASAN/UBSAN/TSAN, folly, unity build, JNI/Java |
Treat these as constraints to satisfy and infer the specifics from them before
adding any system header, libc call, or compiler-specific construct. The most
common trap: anything that compiles under GCC/Clang on Linux but not under
**MSVC/MinGW** -- e.g. unguarded POSIX-only headers/functions (`<unistd.h>`,
`<sys/*.h>`, `getpid`, `_exit`, ...) or GCC/Clang extensions
(`__attribute__`, `__builtin_*`, VLAs, `alloca`). Prefer the `port::`/`Env`
abstractions; otherwise guard with `#ifdef OS_WIN` (POSIX `<unistd.h>` ->
Windows `<process.h>`). Because libc++ is also tested, include what you use
rather than relying on libstdc++ transitive includes.
Do not use `static_assert` to reject a supported architecture for an optional
feature. Check an existing `port::` capability such as `port::kLittleEndian`
and disable only the unsupported feature so the rest of RocksDB still builds.
Use a portable representation when the feature itself must work across
architectures.
### Unit Test
* After all of the unit tests are added, review them and try to extract common
@@ -338,29 +224,14 @@ architectures.
* Don't use sleep to wait for certain events to happen. This will cause test to
be flaky. Instead, use sync point to synchronize thread progress.
* Cap unit test execution with 60 seconds timeout.
* To build and run unit tests locally, prefer these helper scripts:
* `build_tools/rocksptest.sh <test_binary> [more_binaries...] [args...]`
builds the binary(ies) with parallel make and `AUTO_CLEAN=1` and runs
them under gtest-parallel, sharding the test cases across CPUs. Prefer
this whenever running more than a couple of test cases, e.g.
`build_tools/rocksptest.sh table_test` or
`build_tools/rocksptest.sh db_test env_test --gtest_filter=*Foo*`.
* `build_tools/rockstest.sh <test_binary> [args...]` builds with parallel
make and `AUTO_CLEAN=1` and runs the binary directly (serially).
Use it only for a very small number of test cases, e.g.
`build_tools/rockstest.sh db_test --gtest_filter=*MixedSlowdown*`.
* After writing a test, stress-test for flakiness (AUTO_CLEAN handles the
rebuild needed by the `COERCE_CONTEXT_SWITCH=1` flag change):
* When there are multiple unit tests need to be executed, try to use
gtest_parallel.py if available. E.g.
python3 ${GTEST_PARALLEL}/gtest_parallel.py ./table_test
* After writing a test, stress-test for flakiness:
```bash
COERCE_CONTEXT_SWITCH=1 build_tools/rockstest.sh {test_binary} -r100 \
--gtest_filter="*YourTestName*"
COERCE_CONTEXT_SWITCH=1 make {test_binary}
./{test_binary} --gtest_filter="*YourTestName*" --gtest_repeat=5
```
* For CI-style flaky tests that do not reproduce with `gtest_parallel.py`,
`--gtest_repeat`, or normal coerce-mode runs, inspect
`tools/gtest_parallel_repro.py --help`.
* Each unit test file has overheads, so avoid creating new unit test files
for random minor features. Consider adding to slice_test, db_etc3_test, or
others.
### Unit test dedup guidelines
* Extract helper functions for repeated patterns such as object
@@ -381,9 +252,6 @@ architectures.
### Adding new public API
Refer to claude_md/add_public_api.md
### Changing existing public API behavior
Follow the Behavioral Compatibility requirements above.
### Adding new option
Refer to claude_md/add_option.md
@@ -411,27 +279,17 @@ architectures.
### Adding release note
* Release note should be kept short at high level for external user consumption.
Release notes identify what users might care about most in a release. They
are not exhaustive and are not a guide. PLEASE learn from past agents who
tried to build elaborate release notes with implementation details and
elsewhere-documented nuance. That wastes time. Fight the bias that
"my change" is important so must be worthy of release note mention.
* If more than single markdown line, consider how their formatting will be
integrated into HISTORY.md.
### Blog posts (docs/_posts)
* Blog post authors must be defined in `docs/_data/authors.yml` to be displayed
### Final verification of the change
* Execute `AUTO_CLEAN=1 make check` to build all of the changes and execute all
of the tests. `AUTO_CLEAN=1` ensures a clean rebuild if your previous build
used different parameters. Note that executing all of the tests could take
multiple minutes.
* Run `AUTO_CLEAN=1 ASSERT_STATUS_CHECKED=1 make check` to verify all Status
objects are properly checked. This catches missing error handling that can
lead to silent data corruption. Merely building with ASSERT_STATUS_CHECKED=1
accomplishes nothing; it enables a runtime check. New unit tests missing
Status checks is a common failure point, even from agents.
* Execute make clean to clean all of the changes.
* Execute make check to build all of the changes and execute all of the tests.
Note that executing all of the tests could take multiple minutes.
* Run `ASSERT_STATUS_CHECKED=1 make check` to verify all Status objects are
properly checked. This catches missing error handling that can lead to
silent data corruption.
### Monitoring make check progress
* Use `make check-progress` to get machine-parseable JSON progress while
@@ -439,7 +297,7 @@ architectures.
builds without timeout issues.
* Run `make check` in background, then poll progress:
```bash
AUTO_CLEAN=1 make check &
make check &
# Poll periodically:
make check-progress
```
@@ -464,10 +322,9 @@ architectures.
### Executing benchmark using db_bench
* Since the goal is to measure performance, we need to build a release binary
using `AUTO_CLEAN=1 DEBUG_LEVEL=0 make db_bench`. If there is an engine
crash due to a bug, switch back to a debug build with
`AUTO_CLEAN=1 make dbg`; `AUTO_CLEAN=1` handles the release<->debug rebuild
automatically.
using `make clean && DEBUG_LEVEL=0 make db_bench`. If there is an engine
crash due to bug, we need to switch back to debug build. Make sure to run
`make clean` before running `make dbg`.
### Formatting code
* After making change, use `make format-auto` to auto-apply formatting without
+12 -76
View File
@@ -420,6 +420,13 @@ if(WITH_NUMA)
list(APPEND THIRDPARTY_LIBS NUMA::NUMA)
endif()
option(WITH_TBB "build with Threading Building Blocks (TBB)" OFF)
if(WITH_TBB)
find_package(TBB REQUIRED)
add_definitions(-DTBB)
list(APPEND THIRDPARTY_LIBS TBB::TBB)
endif()
# Stall notifications eat some performance from inserts
option(DISABLE_STALL_NOTIF "Build with stall notifications" OFF)
if(DISABLE_STALL_NOTIF)
@@ -647,8 +654,6 @@ if(USE_FOLLY)
FMT_INST_PATH)
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../gflags* OUTPUT_VARIABLE
GFLAGS_INST_PATH)
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../glog* OUTPUT_VARIABLE
GLOG_INST_PATH)
set(Boost_DIR ${BOOST_INST_PATH}/lib/cmake/Boost-1.83.0)
if(EXISTS ${FMT_INST_PATH}/lib64)
set(fmt_DIR ${FMT_INST_PATH}/lib64/cmake/fmt)
@@ -656,61 +661,16 @@ if(USE_FOLLY)
set(fmt_DIR ${FMT_INST_PATH}/lib/cmake/fmt)
endif()
set(gflags_DIR ${GFLAGS_INST_PATH}/lib/cmake/gflags)
if(EXISTS ${GLOG_INST_PATH}/lib64/cmake/glog)
set(GLOG_CMAKE_DIR ${GLOG_INST_PATH}/lib64/cmake/glog)
elseif(EXISTS ${GLOG_INST_PATH}/lib/cmake/glog)
set(GLOG_CMAKE_DIR ${GLOG_INST_PATH}/lib/cmake/glog)
endif()
if(NOT GLOG_CMAKE_DIR)
find_library(GETDEPS_GLOG_LIBRARY NAMES glogd glog
PATHS ${GLOG_INST_PATH}/lib64 ${GLOG_INST_PATH}/lib
NO_DEFAULT_PATH)
if(NOT GETDEPS_GLOG_LIBRARY OR
NOT EXISTS ${GLOG_INST_PATH}/include/glog/logging.h)
message(FATAL_ERROR "Could not find getdeps glog under "
"${GLOG_INST_PATH}")
endif()
set(GLOG_CMAKE_DIR ${CMAKE_CURRENT_BINARY_DIR}/getdeps-glog-cmake)
file(MAKE_DIRECTORY ${GLOG_CMAKE_DIR})
file(WRITE ${GLOG_CMAKE_DIR}/glog-config.cmake
"if(NOT TARGET glog::glog)\n"
" add_library(glog::glog UNKNOWN IMPORTED)\n"
" set_target_properties(glog::glog PROPERTIES\n"
" IMPORTED_LOCATION \"${GETDEPS_GLOG_LIBRARY}\"\n"
" INTERFACE_INCLUDE_DIRECTORIES \"${GLOG_INST_PATH}/include\")\n"
"endif()\n"
"set(glog_FOUND TRUE)\n"
"set(Glog_FOUND TRUE)\n"
"set(GLOG_FOUND TRUE)\n"
"set(GLOG_LIBRARY \"${GETDEPS_GLOG_LIBRARY}\")\n"
"set(GLOG_LIBRARIES \"${GETDEPS_GLOG_LIBRARY}\")\n"
"set(GLOG_INCLUDE_DIR \"${GLOG_INST_PATH}/include\")\n")
endif()
set(glog_DIR ${GLOG_CMAKE_DIR})
set(Glog_DIR ${GLOG_CMAKE_DIR})
# Unlike gflags, glog may also be installed system-wide. Pre-resolve the
# getdeps copy so folly-config.cmake cannot fall back to another version.
find_package(glog CONFIG REQUIRED PATHS ${GLOG_CMAKE_DIR}
NO_DEFAULT_PATH)
exec_program(sed ARGS -i 's/gflags_shared//g'
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
# Fix gflags library name for debug builds. The getdeps gflags shared
# library is versioned (e.g. libgflags_debug.so.2.3), so resolve the
# actual file by glob rather than hard-coding a version.
# Fix gflags library name for debug builds
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
file(GLOB GFLAGS_DEBUG_SHARED_LIBS
"${GFLAGS_INST_PATH}/lib/libgflags_debug.so.*")
if(NOT GFLAGS_DEBUG_SHARED_LIBS)
message(FATAL_ERROR
"Could not find getdeps libgflags_debug.so under ${GFLAGS_INST_PATH}/lib")
endif()
list(GET GFLAGS_DEBUG_SHARED_LIBS 0 GFLAGS_DEBUG_SHARED_LIB)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath=${GFLAGS_INST_PATH}/lib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_DEBUG_SHARED_LIB}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_INST_PATH}/lib/libgflags_debug.so.2.2")
endif()
endif()
@@ -777,7 +737,6 @@ set(SOURCES
db/blob/blob_file_meta.cc
db/blob/blob_file_reader.cc
db/blob/blob_garbage_meter.cc
db/blob/blob_gen2_format.cc
db/blob/blob_log_format.cc
db/blob/blob_log_sequential_reader.cc
db/blob/blob_log_writer.cc
@@ -801,7 +760,6 @@ set(SOURCES
db/compaction/sst_partitioner.cc
db/compaction/subcompaction_state.cc
db/convenience.cc
db/coro_db.cc
db/db_filesnapshot.cc
db/db_impl/compacted_db_impl.cc
db/db_impl/db_impl.cc
@@ -838,7 +796,6 @@ set(SOURCES
db/merge_operator.cc
db/multi_scan.cc
db/output_validator.cc
db/periodic_compaction_phaser.cc
db/periodic_task_scheduler.cc
db/range_del_aggregator.cc
db/range_tombstone_fragmenter.cc
@@ -847,6 +804,7 @@ set(SOURCES
db/snapshot_impl.cc
db/table_cache.cc
db/table_properties_collector.cc
db/transaction_log_impl.cc
db/trim_history_scheduler.cc
db/version_builder.cc
db/version_edit.cc
@@ -854,9 +812,7 @@ set(SOURCES
db/version_set.cc
db/version_util.cc
db/wal_edit.cc
db/wal_iterator_impl.cc
db/wal_manager.cc
db/wide/lazy_wide_columns.cc
db/wide/read_path_blob_resolver.cc
db/wide/wide_column_serialization.cc
db/wide/wide_columns.cc
@@ -938,7 +894,6 @@ set(SOURCES
table/block_based/block_cache.cc
table/block_based/block_prefetcher.cc
table/block_based/block_prefix_index.cc
table/block_based/builtin_index_factory.cc
table/block_based/data_block_hash_index.cc
table/block_based/data_block_footer.cc
table/block_based/filter_block_reader_common.cc
@@ -1006,7 +961,6 @@ set(SOURCES
util/simple_mixed_compressor.cc
util/compression_context_cache.cc
util/concurrent_task_limiter_impl.cc
util/coro_stats_util.cc
util/crc32c.cc
util/data_structure.cc
util/dynamic_bloom.cc
@@ -1028,7 +982,6 @@ set(SOURCES
util/xxhash.cc
utilities/agg_merge/agg_merge.cc
utilities/backup/backup_engine.cc
utilities/copy_engine/copy_engine.cc
utilities/blob_db/blob_compaction_filter.cc
utilities/blob_db/blob_db.cc
utilities/blob_db/blob_db_impl.cc
@@ -1500,7 +1453,6 @@ if(WITH_TESTS)
db/db_clip_test.cc
db/db_dynamic_level_test.cc
db/db_encryption_test.cc
db/db_etc2_test.cc
db/db_etc3_test.cc
db/db_flush_test.cc
db/db_inplace_update_test.cc
@@ -1524,6 +1476,7 @@ if(WITH_TESTS)
db/db_table_properties_test.cc
db/db_tailing_iter_test.cc
db/db_test.cc
db/db_test2.cc
db/db_logical_block_size_cache_test.cc
db/db_universal_compaction_test.cc
db/db_wal_test.cc
@@ -1564,7 +1517,6 @@ if(WITH_TESTS)
db/version_set_test.cc
db/wal_manager_test.cc
db/wal_edit_test.cc
db/wide/db_lazy_entity_test.cc
db/wide/db_wide_basic_test.cc
db/wide/db_wide_blob_direct_write_test.cc
db/wide/wide_column_serialization_test.cc
@@ -1596,7 +1548,6 @@ if(WITH_TESTS)
options/options_test.cc
table/block_based/block_based_table_reader_test.cc
table/block_based/block_test.cc
table/block_based/builtin_index_factory_test.cc
table/block_based/data_block_hash_index_test.cc
table/block_based/full_filter_block_test.cc
table/block_based/partitioned_filter_block_test.cc
@@ -1648,7 +1599,6 @@ if(WITH_TESTS)
utilities/cassandra/cassandra_row_merge_test.cc
utilities/cassandra/cassandra_serialize_test.cc
utilities/checkpoint/checkpoint_test.cc
utilities/copy_engine/copy_engine_test.cc
utilities/sorted_run_builder/sorted_run_builder_test.cc
utilities/env_timed_test.cc
utilities/fault_injection_fs_test.cc
@@ -1716,21 +1666,7 @@ if(WITH_TESTS)
)
target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${THIRDPARTY_LIBS} ${ROCKSDB_LIB})
if(NOT "${exename}" MATCHES "db_sanity_test")
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18)
# Discover tests at ctest time instead of during the parallel build.
# POST_BUILD discovery (the default) runs each freshly-built test
# binary while the -j build is still compiling; on resource-constrained
# CI (notably the arm64 macOS runners) that discovery run can yield
# empty output, and CMake's JSON parser then aborts the whole build
# with "Syntax error: value, object or array expected". PRE_TEST moves
# discovery out of the build phase so a slow/empty enumeration no
# longer fails the build.
gtest_discover_tests(${exename}
DISCOVERY_TIMEOUT 120
DISCOVERY_MODE PRE_TEST)
else()
gtest_discover_tests(${exename} DISCOVERY_TIMEOUT 120)
endif()
gtest_discover_tests(${exename} DISCOVERY_TIMEOUT 120)
add_dependencies(rocksdb_check ${exename}${ARTIFACT_SUFFIX})
endif()
endforeach(sourcefile ${TESTS})
-192
View File
@@ -1,198 +1,6 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.11.1 (09/24/2026)
### Behavior Changes
* `Iterator::Refresh()` now returns `NotSupported` on iterators from a secondary DB's `DB::NewIterators()` only, because those iterators are pinned to one cross-column-family read view. Iterators from `DB::NewIterator()` are unaffected; to advance a batch, call `DB::NewIterators()` again after `TryCatchUpWithPrimary()`.
### Bug Fixes
* Fixed secondary DB `NewIterators()` returning iterators from different database states or blocking for `TryCatchUpWithPrimary()` by serving the last completed catch-up view.
## 11.11.0 (09/21/2026)
### New Features
* External tables now support DB-assigned global sequence numbers when `IngestExternalFileOptions::allow_global_seqno` is enabled, allowing overlapping external tables to be ingested across multiple levels when compactions are disabled. This includes the deprecated `write_global_seqno` path when the reader supplies the properties block's file offset.
* Added checksum handoff support for SST file writes when `BlockBasedTableOptions::checksum` is `kCRC32c`, allowing block and trailer CRC32C checksums to be passed from the table builder to the file writer for buffered-data validation and filesystem in-flight write protection.
* Enhanced `sst_dump --command=recompress` to be more powerful for benchmarking, including through automation with a new `--json` option for machine-readable output.
### Public API Changes
* Changed the experimental async read statistics API by removing `DB::AsyncCallback::EnableStats()` and changing `OnComplete(const PerfContext*, const IOStatsContext*)` to `OnComplete()`. Callers must configure TLS statistics before each async or coroutine read. `DB::GetAsync()` and `DB::MultiGetAsync()` callbacks must copy per-operation statistics from TLS during `OnComplete()`; coroutine users must copy them from inside a `CoroStackableDB` hook before suspending again. Each operation leaves TLS statistics collection disabled.
* Added `Compressor::MaybeOverrideCompressionOptions()` (EXPERIMENTAL), an omnibus hook letting a custom compressor adjust the subset of `CompressionOptions` that the SST builder applies rather than the compressor itself: `parallel_threads`, `max_compressed_bytes_per_kb`, `auto_skip`, and `auto_skip_min_sample_every` (and potentially more in the future). This deprecates `Compressor::GetRecommendedParallelThreads()`, which it subsumes; the default implementation still honors an overridden `GetRecommendedParallelThreads()` for compatibility.
* Added experimental `CoroDB::CoGet()` and `CoroDB::CoMultiGet()` overloads for point reads from an `SstFileReader`.
* Added experimental `DB::GetWithMetadata()` and `DB::MultiGetWithMetadata()` APIs with opt-in output metadata for key timestamps and explicit-snapshot newer-version detection.
* Added a `DB::NewIterators()` overload that accepts parallel vectors of `ReadOptions` and column family handles, allowing each returned iterator to use independent read settings while sharing a consistent database state. The vectors must have the same size, all snapshots must be null or identical, and all `tailing` settings must match.
* Added `ReadOptions::custom_context`, an opaque, non-owning pointer passed through table reads so external table implementations can consume application-defined read options.
### Behavior Changes
* `ExternalTableReader::MultiGet()` is now used by RocksDB's `DB::MultiGet()` path. Previously, the external table adapter used `TableReader`'s fallback, which called `ExternalTableReader::Get()` separately for each key.
* `DB::OpenAndCompact()` now requires `output_directory` to be a simple name for a new subdirectory when the new `use_session_tmp_dir_for_remote_compaction` DB option is enabled. The option places that subdirectory under the canonical `<dbname>/session_tmp/` root, and `DB::Open()` deletes stale contents under the root after recovery. The disabled default preserves the existing `<dbname>/<output_directory>` layout without automatic cleanup. Previously, remote-compaction output directories were created directly under `<dbname>`, which made it challenging to safely identify and delete obsolete files.
* Several block-based SST table properties now better support compression benchmarking and optimization: `uncompressed_data_size` is now persisted in the file (previously it was computed only at write time and read back as 0); a new `lsm_info_at_creation` property records the LSM level and "bottommost" status at the file's creation, distinguishing "not applicable" (e.g. SstFileWriter files) from "unknown" (older files); and the `compression_options` property now records the full compression manager configuration instead of only its name.
### Bug Fixes
* Fixed a usually-minor performance bug where `Cache::ApplyToAllEntries()` used single-entry batches instead of the intended 256-entry default for `ApplyToAllEntriesOptions::average_entries_per_lock`. All of the Cache implementations (LRU and HCC) benefit from the fix, but the disfavored LRU cache was especially impacted by repeated mutex acquisition and release.
* Fixed stats-disabled coroutine and asynchronous reads inheriting enabled TLS statistics state when interleaved with stats-enabled reads on the same executor. Each read now uses its captured per-call configuration, while enabled request counters remain isolated across suspensions.
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) requested with `OpenAndCompactOptions::allow_resumption=true` but for which resumption was internally disabled (incompatible with `paranoid_file_checks` or output-iteration verification) could fail a retried attempt, because the output directory was not cleaned before starting fresh and output files left by a previously interrupted attempt collided with the reused file numbers.
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) could fail when the primary rotated its MANIFEST while the worker was opening the primary's directory read-only. The open reads `CURRENT`, and the primary publishes a new MANIFEST by renaming a freshly written `CURRENT` over the old one; on file systems without POSIX read-after-unlink semantics (remote/object stores) the replaced object is then reported as gone (`PathNotFound`/`NotFound`, or `TryAgain` for the MANIFEST equivalent) instead of remaining readable. `DB::OpenAndCompact()` now retries the secondary open a bounded, configurable number of times for those transient statuses, so a concurrent rotation no longer fails the compaction (which, for a `CompactionService` that does not fall back to a local compaction, became a background error on the primary).
* Fixed secondary DB `NewIterators()` returning iterators from different database states when `TryCatchUpWithPrimary()` runs concurrently.
* Fixed a bug where a `SeekToFirst()` on an iterator previously used for a `Seek()` could end the scan early and silently omit keys with `ReadOptions::async_io`, or otherwise run with readahead effectively disabled. Affects scans using `ReadOptions::prefix_same_as_start` together with `ReadOptions::auto_readahead_size` (enabled by default); memory-mapped reads are unaffected.
### Performance Improvements
* `DB::MultiGetEntityLazy()` now reads all keys of a batch under a single shared per-column-family SuperVersion pin, and a single `LazyWideColumnsBatch::MultiResolve()` coalesces the resulting blob reads across keys: one read per blob file (and one per SST for embedded blobs), for whole-column and byte-range reads alike, instead of reading each key's blobs separately.
## 11.10.0 (08/28/2026)
### New Features
* Added `BlockBasedTableOptions::optimize_key_common_prefix` as part of new `format_version=8`. When enabled, each data block and index block stores the common user-key prefix of its keys once instead of at every restart point, reducing block size and block-cache footprint for prefix-heavy data and speeding up Seek for bytewise-ordered keys.
* A new experimental feature spreads out periodic (time-based) compaction trigger times across a fleet of similarly-configured DBs to avoid recurring "thundering herd" triggers, especially with remote compaction. See the new experimental DB option `periodic_compaction_phase_recovery_percent`.
### Public API Changes
* `IngestExternalFileArg::atomic_replace_range` now supports a range that only partially overlaps existing files, instead of failing. Requires universal compaction and a table factory that supports range deletion; other configurations still fail on partial overlap. The range is also now specified as half-open (`start` inclusive, `limit` exclusive), resolving a previously documented ambiguity in the upper bound.
* Added an immutable `external_table_config` option that is persisted in OPTIONS files and passed to external table factories during bootstrap.
### Bug Fixes
* Fixed a liveness bug where RocksDB could remain indefinitely in recovery from a soft background error when concurrent writes continuously generated normal flush requests.
* Fixed a race in online error recovery where a concurrent higher-severity I/O error could leave a stale retry-flush plan active, prevent the required WAL switch, and create duplicate internal WAL history detected on DB reopen.
* Fix "bottommost files" infinite compaction loops (`kBottommostFiles`). This happens with the unsupported combination of leveled compaction and `preserve_internal_time_seconds` / `preclude_last_level_data_seconds`, and in an edge case with user-defined timestamps when `full_history_ts_low` is unset. The underlying logic is now more unified to reduce risk of a similar divergence in logic in the future.
* Fixed `reuse_manifest_on_open` to create a fresh MANIFEST instead of appending after recovery sees an incomplete atomic group at the MANIFEST tail.
* Starting remote compaction (`CompactionService` / `DB::OpenAndCompact`) is now more efficient and more reliable, by avoiding unnecessary file operations (latency) while using more accurate consistency / precondition checks, both avoiding some spurious failures and rejecting more legitimate inconsistencies. New DBOption `remote_compaction_manifest_floor` can be disabled in case of unexpected problems with the change.
* Fixed unbounded retention of recovered transaction state by a long-lived secondary opened with `allow_2pc`.
* Fixed stale reads on secondary instances, where entries replayed from a WAL that the primary had already flushed could shadow the newer values in the flushed files indefinitely. Also fixed a case where a secondary could stop returning such a key altogether while the primary's flushed file holding it was unreadable.
* Fixed subset checkpoints to discard tolerated MANIFEST tail corruption before appending column-family drop records.
### Performance Improvements
* Improved compaction CPU efficiency for column families using an `SstPartitioner` (such as the built-in `SstPartitionerFixedPrefix`): when the partitioner can express its next boundary as a key prefix via the new `SstPartitioner::ShouldPartitionByPrefix()` API, compaction now checks that cached prefix inline instead of copying every user key and making a virtual `ShouldPartition()` call for each key-value written to an SST.
## 11.9.0 (08/14/2026)
### New Features
* Added an integrated and improved compression auto-skip feature (new CompressionOptions, still experimental) that saves CPU by skipping compression when and where the overall ratio is inadequate. This improves on the previous auto-skip compression manager by (a) taking achieved compression ratios into account, (b) using a continuously evolving payoff estimate, and (c) combining natively with parallel compression.
* Added the `blob_file_writable_file_max_buffer_size` mutable column family option. When set to a non-zero value, blob-file writers use it as their `WritableFileWriter` max buffer size instead of inheriting `DBOptions::writable_file_max_buffer_size`.
* Added an experimental lazy wide-column read API, `DB::GetEntityLazy()` / `DB::MultiGetEntityLazy()` (see `include/rocksdb/lazy_wide_columns.h`). The returned `LazyWideColumns` leaves blob-backed columns as unresolved references and, like an iterator, stays valid after the call returns, so the caller resolves only the columns -- and only the byte ranges -- it needs; a byte-range read of an uncompressed blob (separate-file or embedded) reads just those bytes from storage. Adds `Env::IOActivity::kLazyResolve` and `rocksdb.blobdb.lazy.*` statistics. Requires `max_open_files == -1`.
* Added DB property `rocksdb.num-running-remote-compactions`, reporting the number of compaction service jobs currently waiting in `CompactionService::Wait()`.
* Added DB property `rocksdb.num-unscheduled-compactions`, reporting compactions waiting in the DB's internal compaction queue but not yet assigned to a background job.
* Added `DB::AbortCompactions()` and `DB::ResumeCompactions()` for per-column-family compaction abort and resume, with the same active-abort semantics as the DB-wide `AbortAllCompactions()`/`ResumeAllCompactions()` but scoped to a single column family. Calls are reference-counted independently per column family and compose with the DB-wide abort.
### Public API Changes
* Add IndexFactory compatibility names for the existing user-defined index API.
* Added the experimental `CoroDB` capability, exposed by `DB::GetCoroDB()`, with lazy native coroutine variants of `Get` and `MultiGet`. The existing `DB::GetAsync()` and `DB::MultiGetAsync()` APIs dispatch through it.
* Added `rocksdb_batched_multi_get_pinned_cf()` to return batch-owned pinned MultiGet values and per-key errors without allocating one wrapper per found value.
* Add `DBOptions::read_io_executor_threads` to increase the maximum thread count of the shared filesystem read I/O executor used for asynchronous reads when opening a DB. Opening a DB never reduces the shared executor thread count. Change the experimental `FSRandomAccessFile::SubmitReadAsync()` API to return whether the asynchronous path was used, and add the `FILE_SUBMIT_ASYNC_READ_FALLBACK` ticker to count calls that fall back to synchronous reads.
* `ReadOptions::table_filter` is now a *pointer* to a caller-owned `std::function` instead of a `std::function` value, to improve efficiency in copying and destroying `ReadOptions` objects (which RocksDB does internally). This is a breaking change for users of this relatively obscure option, who will now need to declare a `std::function` that outlives the read. As a side effect of `ReadOptions` being trivially copyable, new but legitimate compiler warnings may surface.
* Removed the experimental `CreateAutoSkipCompressionManager()`. Auto-skip compression is now a first-class feature of the block-based table builder, controlled by the new experimental `CompressionOptions::auto_skip`.
* Added `CoroStackableDB` for implementing wrappers over native coroutine reads and used it to add coroutine `Get` and `MultiGet` support to `DBWithTTL` and WritePrepared `TransactionDB`. A stackable wrapper advertises coroutine support only when its wrapped DB supports it; TTL value decoding and transaction visibility semantics are preserved.
* Added a `kSstFileWriter` value to `TableFileCreationReason`, which `SstFileWriter` now reports instead of the catch-all `kMisc`. This, along with new `db_name` and `is_remote_compaction` fields in `FilterBuildingContext`, improves the contextual information available to custom filter and compression strategies.
* Renamed `TransactionLogIterator` to `WalIterator` (and `include/rocksdb/transaction_log.h` to `include/rocksdb/wal_iterator.h`), because it iterates the write-ahead log and is unrelated to `TransactionDB`. The old names still work. `DB::GetUpdatesSince()` is unchanged. In Java, `WalIterator` is the new name and `TransactionLogIterator` remains as a subclass. Also documented existing behavior that was previously unstated: only writes that reached the WAL are visible (so `WriteOptions::disableWAL` and `IngestExternalFile()` leave holes that end iteration), what each iterator state means, and that `GetUpdatesSince()` silently starts later when the requested sequence number is unavailable.
### Bug Fixes
* Fixed a bug where `BlobFileBuilder` passed an SST path to `OnBlobFileCompleted()` instead of the completed blob path during BlobDB garbage-collection compactions, causing incorrect `SstFileManager` accounting and compaction failures on filesystems where the SST was not yet pathname-visible.
### Performance Improvements
* When `DBOptions::avoid_unnecessary_blocking_io` is true, obsolete `OPTIONS-*` files found during DB open are deleted by background purge instead of synchronously on the opening thread.
## 11.8.0 (07/28/2026)
### Public API Changes
* Add callback-based asynchronous read APIs, `DB::GetAsync()` and `DB::MultiGetAsync()`. The idea is that when IO is required, RocksDB can suspend its internal coroutine read path, allowing the read executor thread to do other work. When the IO is complete, RocksDB invokes the user callback. This requires filesystem support for full performance benefits. A new filesystem API `FSRandomAccessFile::SubmitReadAsync()` is introduced for this. Unlike `ReadAsync`, the filesystem is responsible for eventually calling the callback. `FileSystem::GetReadExecutor()` returns the IO executor whose EventBases run coroutine read processing. In the Posix filesystem, an IO-uring based event loop is used to complete the IO.
* Added `FlushOptions::listener_wait` (default `false`). When set together with `FlushOptions::wait == true`, `DB::Flush()` will not return until the registered `EventListener::OnFlushCompleted` callbacks for the flushed memtables have finished running. By default (`false`), `Flush(wait=true)` may return as soon as the flush result is committed, which can be before (or while) the `OnFlushCompleted` callbacks execute on the background flush thread. Also added the corresponding C API `rocksdb_flushoptions_set_listener_wait()` / `rocksdb_flushoptions_get_listener_wait()`.
* Deprecated `PinnableWideColumns::serialized_size()` in favor of the new `PinnableWideColumns::payload_size()`, which returns the total size of the columns' names and values (for a plain value this equals the value size). Relatedly, wide-column read accounting changed subtly: read statistics for entities (`BYTES_READ`, `BYTES_PER_READ`, and the per-key byte counts for `GetEntity`/`MultiGetEntity`) and the `ReadOptions::value_size_soft_limit` threshold now measure entities by this payload size instead of the serialized entity size. This drops serialization framing bytes from those measurements (so reported/limited sizes for entity reads are slightly smaller than before) and makes plain-value and wide-column accounting consistent.
### Behavior Changes
* `ReadOptions::value_size_soft_limit` now also bounds the blob values resolved from pre-flush blob direct write references during `MultiGet`/`MultiGetEntity` (previously these were resolved after the limit check and read without bound). Enforcement is "always make progress": at least one key is read even if its value alone exceeds the limit, and only once the returned size exceeds the limit do subsequent keys get `Status::Aborted` -- so a caller retrying the aborted keys cannot loop forever on a single value that by itself exceeds the limit.
### Performance Improvements
* Reduced copying when reading wide-column entities that have blob-referenced columns, whether stored in separate blob files or embedded (same-file) as blob records in the same SST. On the point-lookup (`GetEntity`/`MultiGetEntity`) and blob-backed memtable/direct-write read paths, resolving these columns no longer re-serializes the whole entity into a fresh buffer: inline columns are referenced in place in the pinned entity and each resolved blob value is referenced in place from its fetched buffer. This is enabled by an internal change to `PinnableWideColumns` that lets its columns reference multiple backing buffers.
## 11.7.0 (07/16/2026)
### Public API Changes
* Added `RangeLockManagerHandle::SetIsKilledCallback()`, a `std::function<bool()>` predicate polled during range-lock waits. When it returns true, a blocked `GetRangeLock()` wait returns promptly with `Status::Aborted()` instead of waiting for the lock timeout. Range-lock users that install no callback keep the original wait-until-grant-or-timeout behavior.
* Added `MultiScanArgs::reverse` to support reverse MultiScan reads over bounded scan ranges.
### Behavior Changes
* When using `LockWAL()`, live-file capture APIs such as checkpoint and backup now flush WAL-disabled unpersisted data; they can block until `UnlockWAL()` or return `Aborted` when called from the same thread that holds `LockWAL()` to avoid deadlock. `UnlockWAL()` must now also be called from the same thread that called `LockWAL()`.
### Performance Improvements
* Fixed slow DB open with `best_efforts_recovery`, and slow catch-up of secondary and follower (read-only) instances, in which time and memory grew quadratically with the number of files in the DB (so the slowdown was worst for large DBs with long history).
## 11.6.0 (07/02/2026)
### New Features
* Added EXPERIMENTAL embedded blob SST support through `SstFileWriter::OpenWithEmbeddedBlobs()`, storing eligible large values as same-file blob records in block-based SST files and resolving them transparently for reads. This niche feature currently supports uncompressed embedded blobs only; compression options are placeholders and compression support is deferred to follow-up work.
### Public API Changes
* Expanded the C API (`include/rocksdb/c.h`) with a large set of new `rocksdb_*` functions, mostly option getters/setters plus table-properties, job/event-listener, and metadata accessors, a WAL filter, a ReadOptions table filter, and a backup exclude-files callback. Many are now produced by a new semi-automated generator (`tools/c_api_gen/`) from the C++ headers; `include/rocksdb/c.h` remains a single self-contained header and the signatures of pre-existing functions are unchanged.
### Bug Fixes
* Reverted PR14831 that made range_lock_manager aware of reverse-order CF
* Fixed a bug in `RandomAccessFileReader::ReadAsync` where an already-aligned direct-IO read request with a null `scratch` and a caller-provided `aligned_buf` would take the "already aligned" fast path and submit the null buffer to the underlying async read (e.g. a null iovec base to io_uring, failing with EFAULT). This could surface as spurious iterator failures during async prefetch (MultiScan with async IO) on direct-IO databases. The async path now allocates a backing buffer in this case, matching the synchronous `Read` path.
* Fixed a bug where closing a read-only DB instance could delete live SST files created by a concurrent read-write DB sharing the same directory.
## 11.5.0 (06/16/2026)
### New Features
* External table readers that open files through `ExternalTableOptions::fs` now update RocksDB SST/file-read statistics and file IO listener callbacks, making external file IO activity visible in existing read metrics.
* Added `DB::PrepareFileIngestion()`, a two-phase form of `IngestExternalFile`/`IngestExternalFiles`. `PrepareFileIngestion()` performs all of the work that does not require the DB mutex (validating the arguments, reading each external file's metadata, and linking/copying the files into the DB) and returns an opaque `FileIngestionHandle`. `DB::CommitFileIngestionHandle()` (or `DB::CommitFileIngestionHandles()` for several handles at once) then makes the prepared files visible; multiple handles are committed atomically in a single MANIFEST write, or none are. `FileIngestionHandle::Abort()` (or simply destroying the handle) cancels a prepared ingestion and rolls it back. This gives flexibility to the application to Prepare a file separately from when its committed, and may shorten an applications critical path on `IngestExternalFile`.
* Added two stats histograms reporting the per-call latency (in microseconds) of each successful `IngestExternalFile`/`IngestExternalFiles` call, split by phase: `rocksdb.ingest.external.file.prepare.micros` (argument validation and reading/validating/linking the external files, which does not block live writes) and `rocksdb.ingest.external.file.run.micros` (the ingestion performed under the DB mutex while live writes are blocked). Failed ingestions are not recorded.
* Added `DBOptions::use_direct_io_for_compaction_reads` (default false). When enabled, compaction-input SST reads use `O_DIRECT` while user reads remain buffered, avoiding page-cache eviction of hot user-read data by sequential compaction scans. Pair with `use_direct_io_for_flush_and_compaction = true` on write-heavy workloads for direct I/O on both compaction inputs and outputs. Rejected at Open when combined with `allow_mmap_reads = true`. No-op when `use_direct_reads = true` is already set (since all reads are already direct).
### Public API Changes
* `DB::GetPreparedFileInfoForExternalSstIngestion()` can now prepare metadata for a live DB-generated SST file so it can be passed to `IngestExternalFileArg::file_infos`. The input path must exactly match a live table file owned by the source DB, and the returned metadata handle must outlive the ingestion.
* `ExternalSstFileInfo` gained a `prepared_file_info` member produced by `SstFileWriter::Finish`, and `IngestExternalFileArg` gained a `file_infos` field: a vector of borrowed `const PreparedFileInfo*` pointers, parallel to `external_files`. The owning `ExternalSstFileInfo::prepared_file_info` handle must outlive the ingestion. When set, `IngestExternalFiles()` reuses the supplied per-file metadata instead of re-opening and scanning each file to recompute it, avoiding that extra I/O.
* Corrected the public C++ option spelling from `memtable_veirfy_per_key_checksum_on_seek` to `memtable_verify_per_key_checksum_on_seek`. RocksDB continues to accept the old misspelled OPTIONS-file key for compatibility, while newly serialized OPTIONS files use the corrected key.
* Added experimental read-scoped block buffer provider API, configured through `ReadOptions::read_scoped_block_buffer_provider`, for supported block-based table iterator scans and MultiScan reads to use caller-provided read-scoped storage for final data-block contents. When configured, supported provider-backed scan data-block reads bypass the data-block cache. The provider is ignored when mmap reads are enabled. RocksDB may still use ordinary temporary scratch for serialized block bytes, such as when a block may be compressed. Get and MultiGet do not currently provide API guarantees for this provider.
* Added `FileSystem::SyncFile()` and `Env::SyncFile()` public APIs for syncing or fsyncing a file by name without requiring callers to reopen it as writable.
### Behavior Changes
* The default `compression` for column families is now `kLZ4Compression` rather than `kSnappyCompression`. This affects only column families that do not explicitly set `compression`, and only newly-written SST files. The change is fully compatible: existing data remains readable with no migration needed, as RocksDB selects the decompressor per block. LZ4 offers slightly better compression ratios and decompression CPU efficiency vs. Snappy and similar compression CPU, tested across various server CPUs. When support is not compiled in, the fallback path for default compression is LZ4 -> Snappy -> NoCompression.
* Disable parallel compression (ignore `CompressionOptions::parallel_threads`) for fast built-in compressors: Snappy, LZ4 (accelerated, not LZ4HC), and accelerated levels of ZSTD (level < 0). These do not generally benefit from parallel compression. This behavior can be overridden with a custom Compressor from a custom CompressionManager.
* `PosixFileSystem::OptimizeForCompactionTableRead` now delegates to the base `FileSystem::OptimizeForCompactionTableRead` implementation before applying its Linux-only compaction-readahead clamp (added for #12038). The returned `FileOptions::use_direct_reads` therefore reflects `DBOptions::use_direct_reads`, consistent with the base class and with other `FileSystem` implementations. Previously the override ignored the base implementation and keyed both the returned options and the Linux readahead clamp off the incoming `FileOptions` rather than `DBOptions`. At the in-tree call site the incoming `FileOptions::use_direct_reads` already matches `DBOptions::use_direct_reads`, so this is effectively a no-op for existing configurations; it removes a latent inconsistency where a caller passing `FileOptions` whose `use_direct_reads` disagreed with `DBOptions` would have had the global flag silently ignored for compaction-input reads on Linux. (Note: the `#12038` readahead clamp still keys off the global `use_direct_reads`; it is not skipped for the compaction-only `use_direct_io_for_compaction_reads` path, since that flag enables O_DIRECT after this hook runs.)
* Brought more unity to `kLZ4Compression` and `kLZ4HCCompression` by giving each access to the other's compression levels. Negative (fast) values previously only available to LZ4 and positive (slow) values previously only available to LZ4HC are now available to both, so configuring a non-default `compression_opts.level` now selects the LZ4 compressor variant. This is a behavior change for previously tolerated but dubious configurations such as positive compression level with `kLZ4Compression`. `kLZ4Compression` and `kLZ4HCCompression` keep their effective default levels, equivalent to -1 and 9 respectively.
* Removed a discontinuity in `kZSTD` compression levels at `compression_opts.level == 0` by mapping that setting to `level = -1` rather than to `level = 3` as the ZSTD library does internally. This improves the compression auto-tuning landscape.
### Bug Fixes
* Fixed a bug where the range lock manager would crash with an assertion failure when using a reverse comparator column family.
### Performance Improvements
* Reduced commit latency when ingesting many external files by allowing `IngestExternalFileOptions::file_opening_threads` to open table readers for committed ingested files using multiple threads.
* Reduced commit latency for large external file ingestions into the last level by adding `IngestExternalFileOptions::prefetch_lmax_index_and_filter_blocks`, which can skip commit-time index and filter block prefetching for cache-backed table metadata.
## 11.4.0 (06/02/2026)
### Public API Changes
* Added `rocksdb_options_set_memtable_batch_lookup_optimization()` and `rocksdb_options_get_memtable_batch_lookup_optimization()` to the C API, exposing the existing `AdvancedColumnFamilyOptions::memtable_batch_lookup_optimization` field. This allows C API users (and downstream language bindings) to enable the skip-list memtable's batch-lookup optimization for `MultiGet`, which caches the search path between consecutive keys and reduces per-key cost from O(log N) to O(log d) where d is the distance between consecutive keys.
* Added `rocksdb_readoptions_set_optimize_multiget_for_io()` and `rocksdb_readoptions_get_optimize_multiget_for_io()` to the C API, exposing the existing `ReadOptions::optimize_multiget_for_io` field. This allows C API users (and downstream language bindings) to opt out of the multi-level parallel MultiGet path, which only takes effect when the library is built with `USE_COROUTINES`.
* Added `rocksdb_block_based_table_index_block_search_type_auto` enum constant and the `rocksdb_block_based_options_set_uniform_cv_threshold()` setter to the C API. The constant exposes `BlockSearchType::kAuto`, and the setter exposes `BlockBasedTableOptions::uniform_cv_threshold`. Both are required for `kAuto` index-block search to take effect from the C API: without setting `uniform_cv_threshold >= 0`, the per-block `is_uniform` footer bit is never written, and `kAuto` falls back to binary search at read time.
* Added `EventListener::OnDBShutdownBegin`, a callback that fires once when a DB begins shutdown, including when RocksDB cleans up a failed `DB::Open()` attempt.
* Added a new PerfContext counter `blob_cache_read_byte` for blob cache bytes read.
### Behavior Changes
* Remote compaction now falls back to local compaction when the primary cannot deserialize a successful `CompactionServiceResult` before installing any remote output files.
* StringToMap (rocksdb/convenience.h) now preserves the outer braces of nested values so each map entry is in self-contained form and can be embedded directly into another `key=value;` string (e.g. SetOptions) without losing inner ';' delimiters. A new symmetric MapToString utility is provided.
### Bug Fixes
* Fixed a bug where `AbortAllCompactions()` could leave automatic compaction work unscheduled when aborting before the background worker picked it, causing compaction and write stalls until DB restart.
* Fix a bug where db had a false positive compaction corruption error due to remote compaction service result with `has_accurate_num_input_records=false` not being serialized.
* Fixed WritePrepared TransactionDB cleanup after retryable commit or rollback write failures so prepared transactions remain rollbackable instead of leaving unresolved prepared state.
* Fix a rare corruption bug for tiered compaction that incorrectly moved last level range tombstones into proximal level. This corruption error is surfaced when force_consistency_checks is enabled.
### Performance Improvements
* Reduce the likelihood of user-facing metadata operations blocking on MANIFEST rotation when file creation is slow, such as on remote storage. MANIFEST write batches containing foreground edits from `CreateColumnFamily()`, `DropColumnFamily()`, `CreateColumnFamilyWithImport()`, `IngestExternalFile()`, and `DeleteFilesInRanges()` now get a relaxed file size threshold for triggering MANIFEST rotation; background-only MANIFEST write batches, such as compaction and flush, continue to use the normal threshold. This change should make auto-tuning manifest file size more attractive (see `max_manifest_file_size` and `max_manifest_space_amp_pct` options).
## 11.3.0 (05/15/2026)
### New Features
* Add experimental DB option `async_wal_precreate` to precreate the next WAL file in a background thread and reduce foreground WAL rotation latency. The option is sanitized to false when WAL recycling is enabled.
* Added a new `EventListener::OnCompactionPreCommit` callback that fires after a compaction job finishes but before its input files are released (i.e. while `FileMetaData::being_compacted` is still true). Listeners that maintain bookkeeping of which files are currently being compacted can clean up such state in this new callback to avoid races with concurrent compaction picking, where another thread might pick up the same files for a new compaction immediately after `being_compacted` is flipped back to false but before `OnCompactionCompleted` fires. The default implementation is a no-op so this is not a breaking change.
* Add mutable DBOption `optimize_manifest_for_recovery` (default false). When enabled, RocksDB can reduce recovery work after a clean shutdown, which may lower DB::Open latency on warm reopens.
* Added public utility APIs `ParseCompressionNameForDisplay()` to convert `TableProperties::compression_name` into a human-readable compression name for both legacy and format_version 7+ SST metadata, including custom `CompressionManager`-provided display names for custom compression types.
* Add `reuse_manifest_on_open` DBOption (default false). When enabled, DB::Open reuses the existing MANIFEST file for append instead of creating a fresh one, avoiding the cost of serializing the entire database state into a new MANIFEST on the first post-open write. To prevent this feature from interfering with manifest file size auto-tuning, an extra forward-compatible field is now always added to the MANIFEST (to track the last "compacted" size).
### Behavior Changes
* Read-only open with `error_if_wal_file_exists=true` now tolerates empty WAL files so empty precreated WALs do not prevent inspection.
* WriteCommitted TransactionDB now matches WritePrepared and WriteUnprepared compaction filtering in both single- and two-write-queue modes: a compaction filter's FilterMergeOperand will not be invoked on merge operands at or below the latest published sequence number.
### Bug Fixes
* Fixed blob-backed wide-column merge reads to preserve correct status
propagation and resolution across memtable, read-only, and secondary DB
paths.
* Fixed a bug where `DB::GetCreationTimeOfOldestFile()` could return inaccurate results instead of the real creation time when called shortly after opening a legacy DB (one whose manifest lacks `file_creation_time`) with `open_files_async = true`. The API now waits for background SST file loading to complete only when needed; modern DBs are unaffected.
* Fixed merge reads against wide-column/blob-backed base values to preserve precise failure statuses, including `GetMergeOperands()` and direct-write memtable reads.
* Fix bug in range tombstone synthesis that covers live keys added during an IngestExternalFile
* Reject the empty string as a column family name in `DB::CreateColumnFamily` / `DB::CreateColumnFamilies`. Previously such calls returned OK and a usable handle, but the column family was not persisted in the manifest, so any data written to it was silently lost on DB reopen.
* Fixed a bug where a WriteCommitted TransactionDB using commit-bypass WBWI ingestion could drop an entry that is still visible at the published sequence boundary.
## 11.2.0 (04/18/2026)
### New Features
* Added experimental `DBOptions::fast_sst_open` option. When enabled, RocksDB retrieves opaque file system metadata for SST files after flush, compaction, and external file ingestion, persists it in the MANIFEST, and passes it back to the file system on subsequent file opens to accelerate DB open time.
+65 -400
View File
@@ -86,20 +86,11 @@ else
LIB_MODE?=shared
endif
# Set by the gen_check_scripts wrappers to mark a recursive make reusing the
# parent's make_config.mk: silence the parse-time banners and skip
# build_detect_platform (below).
$(if $(ROCKSDB_RECURSIVE_MAKE_SAME_CONFIG),,$(info $$DEBUG_LEVEL is $(DEBUG_LEVEL), $$LIB_MODE is $(LIB_MODE)))
$(info $$DEBUG_LEVEL is $(DEBUG_LEVEL), $$LIB_MODE is $(LIB_MODE))
# Detect what platform we're building on.
# Export some common variables that might have been passed as Make variables
# instead of environment variables.
#
# Skipped by the gen_check_scripts wrappers (which run concurrently under
# gnu_parallel): each would otherwise rewrite the shared make_config.mk
# non-atomically and a concurrent reader could catch it mid-write ("missing
# separator"). Requires an existing make_config.mk, so a normal build still detects.
ifeq ($(and $(ROCKSDB_RECURSIVE_MAKE_SAME_CONFIG),$(wildcard make_config.mk)),)
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
@@ -113,9 +104,8 @@ dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
export USE_FOLLY="$(USE_FOLLY)"; \
export USE_FOLLY_LITE="$(USE_FOLLY_LITE)"; \
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk" $(if $(ROCKSDB_RECURSIVE_MAKE_SAME_CONFIG),2>/dev/null)))
endif
# this file (generated above, or reused when skipped) sets build flags and sources
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
# this file is generated by the previous line to set build flags and sources
include make_config.mk
# Figure out optimize level.
@@ -191,7 +181,7 @@ ifeq ($(filter -DROCKSDB_ASSERT_STATUS_CHECKED,$(OPT)),)
endif
endif
$(if $(ROCKSDB_RECURSIVE_MAKE_SAME_CONFIG),,$(warning Warning: Compiling in debug mode. Don't use the resulting binary in production))
$(warning Warning: Compiling in debug mode. Don't use the resulting binary in production)
endif
# `USE_LTO=1` enables link-time optimizations. Among other things, this enables
@@ -298,7 +288,7 @@ endif
endif
export JAVAC_ARGS
CLEAN_FILES += rocksdb.pc
CLEAN_FILES += make_config.mk rocksdb.pc
ifeq ($(V), 1)
$(info $(shell uname -a))
@@ -337,9 +327,6 @@ missing_make_config_paths := $(shell \
$(foreach path, $(missing_make_config_paths), \
$(warning Warning: $(path) does not exist))
# This (the first rule) must depend on "all".
default: all
ifeq ($(PLATFORM), OS_AIX)
# no debug info
else ifneq ($(PLATFORM), IOS)
@@ -498,6 +485,9 @@ ifdef ROCKSDB_MODIFY_NPHASH
PLATFORM_CXXFLAGS += -DROCKSDB_MODIFY_NPHASH=1
endif
# This (the first rule) must depend on "all".
default: all
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
-Wunused-parameter
@@ -589,12 +579,9 @@ TESTS += $(PLUGIN_TESTS)
ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
# TODO: add/support JNI headers
DEV_HEADER_DIRS := $(sort include/ $(dir $(ALL_SOURCES)))
FOLLY_DEPENDENT_HEADERS := \
include/rocksdb/coro_db.h \
include/rocksdb/utilities/coro_stackable_db.h
# Some headers like in port/ are platform-specific
DEV_HEADERS_TO_CHECK := $(filter-out $(FOLLY_DEPENDENT_HEADERS), $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/'))
PUBLIC_HEADERS_TO_CHECK := $(filter-out $(FOLLY_DEPENDENT_HEADERS), $(shell $(FIND) include/ -type f -name '*.h'))
DEV_HEADERS_TO_CHECK := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/')
PUBLIC_HEADERS_TO_CHECK := $(shell $(FIND) include/ -type f -name '*.h')
else
DEV_HEADERS_TO_CHECK :=
PUBLIC_HEADERS_TO_CHECK :=
@@ -777,6 +764,7 @@ util/build_version.cc: $(filter-out $(OBJ_DIR)/util/build_version.o, $(LIB_OBJEC
$(AM_V_at)$(gen_build_version) > $@
endif
CLEAN_FILES += util/build_version.cc
default: all
#-----------------------------------------------
@@ -821,8 +809,7 @@ endif # PLATFORM_SHARED_EXT
.PHONY: check clean coverage ldb_tests package dbg gen-pc build_size \
release tags tags0 valgrind_check format static_lib shared_lib all \
rocksdbjavastatic rocksdbjava install install-static install-shared \
uninstall analyze tools tools_lib check-headers checkout_folly clang-tidy \
check-c-api-c_test
uninstall analyze tools tools_lib check-headers checkout_folly clang-tidy
# Auto-configure git hooks on first build so developers do not need to run
# "make install-hooks" manually. This is a no-op if already set.
@@ -900,43 +887,23 @@ coverage: clean
parallel_tests = $(patsubst %,parallel_%,$(PARALLEL_TEST))
.PHONY: gen_parallel_tests $(parallel_tests)
# Shard size controls how many test cases run per process. The actual number
# of shards per binary is: min(ceil(test_count / shard_size), NCORES * 8)
# of shards per binary is: min(ceil(test_count / GTEST_SHARD_SIZE), NCORES * 8)
# This adapts to machine size: many small shards on beefy machines, fewer
# larger shards on CI (typically 2-4 cores).
GTEST_SHARD_SIZE ?= 10
NCORES ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
# Per-binary overrides for GTEST_SHARD_SIZE, used to chop up binaries whose
# individual test cases are slow enough that the default of 10 leaves one
# shard on the critical path of "make check". Format: whitespace-separated
# tokens "BINARY:SIZE". Sizes were tuned from `make suggest-slow-tests`
# output to keep max shard time under ~20s on a beefy box. Reducing too far
# adds per-shard fixed overhead, so don't use 1 unless individual tests are
# >5s.
SHARD_SIZE_OVERRIDES ?= \
point_lock_manager_stress_test:1 \
external_sst_file_test:1 \
external_sst_file_basic_test:2 \
db_test:3 \
compaction_service_test:1
$(parallel_tests):
$(AM_V_at)TEST_BINARY=$(patsubst parallel_%,%,$@); \
TEST_COUNT=` \
(./$$TEST_BINARY --gtest_list_tests 2>/dev/null || echo " list_failure") \
| grep -c '^ '`; \
if [ "$$TEST_COUNT" -le 0 ]; then TEST_COUNT=1; fi; \
SHARD_SIZE=$(GTEST_SHARD_SIZE); \
for o in $(SHARD_SIZE_OVERRIDES); do \
case "$$o" in \
"$$TEST_BINARY":*) SHARD_SIZE=$${o#*:} ;; \
esac; \
done; \
MAX_SHARDS=$$(( $(NCORES) * 8 )); \
NUM_SHARDS=$$(( (TEST_COUNT + SHARD_SIZE - 1) / SHARD_SIZE )); \
NUM_SHARDS=$$(( (TEST_COUNT + $(GTEST_SHARD_SIZE) - 1) / $(GTEST_SHARD_SIZE) )); \
if [ "$$NUM_SHARDS" -gt "$$MAX_SHARDS" ]; then NUM_SHARDS=$$MAX_SHARDS; fi; \
if [ "$$NUM_SHARDS" -le 0 ]; then NUM_SHARDS=1; fi; \
echo " Generating $$NUM_SHARDS shards for $$TEST_BINARY ($$TEST_COUNT tests, shard_size=$$SHARD_SIZE)"; \
echo " Generating $$NUM_SHARDS shards for $$TEST_BINARY ($$TEST_COUNT tests)"; \
SHARD_IDX=0; \
while [ "$$SHARD_IDX" -lt "$$NUM_SHARDS" ]; do \
if [ -n "$(CI_TOTAL_SHARDS)" ] && [ $$(( $$SHARD_IDX % $(CI_TOTAL_SHARDS) )) -ne $(CI_SHARD_INDEX) ]; then \
@@ -962,116 +929,31 @@ gen_parallel_tests:
$(AM_V_at)$(FIND) t -type f -name 'run-*' -exec rm -f {} \;
$(MAKE) $(parallel_tests)
# Auxiliary (non-unit-test) checks that are part of `make check`. Kept as a
# single list so the same set can run as parallel jobs alongside the C++ unit
# tests (see gen_check_scripts + check_0) and serially in the J=1 fallback.
CHECK_EXTRA_TARGETS =
ifneq ($(PLATFORM), OS_AIX)
CHECK_EXTRA_TARGETS += check-all-python
ifndef ASSERT_STATUS_CHECKED # not yet working with these tests
CHECK_EXTRA_TARGETS += ldb_tests db_crashtest_tests rocksdb_dump_test
endif
endif
ifndef SKIP_FORMAT_BUCK_CHECKS
CHECK_EXTRA_TARGETS += check-format check-buck-targets check-sources check-workflow-yaml check-c-api-gen
endif
# Generate a t/run-<target> wrapper per enabled auxiliary check; check_0's
# `$(FIND) t -name 'run-*'` then runs them as gnu_parallel jobs alongside the
# unit tests, each framed with a gtest-style pass/fail line. Under CI sharding,
# round-robin across shards so each check runs on exactly one shard. Kept
# separate from gen_parallel_tests (which cleans t/run-* first) so the valgrind_*
# targets, which also glob t/run-*, don't pick these up.
.PHONY: gen_check_scripts
gen_check_scripts:
$(AM_V_at)mkdir -p t; \
idx=0; \
for tgt in $(CHECK_EXTRA_TARGETS); do \
if [ -z "$(CI_TOTAL_SHARDS)" ] || [ $$(( idx % $(CI_TOTAL_SHARDS) )) -eq $(CI_SHARD_INDEX) ]; then \
s=t/run-$$tgt; \
printf '%s\n' \
'#!/bin/sh' \
'# Generated by `make gen_check_scripts`.' \
"t=$$tgt" \
'# Drop the parent jobserver from MAKEFLAGS (its fds are lost through' \
'# gnu_parallel, causing a spurious warning); keep overrides after "--".' \
'MAKEFLAGS=$$(printf "%s" "$$MAKEFLAGS" | sed -e "s/ --jobserver-auth=[^ ]*//g" -e "s/ --jobserver-fds=[^ ]*//g" -e "s/ -j[0-9]*//g")' \
'export MAKEFLAGS' \
'echo; echo "[----------]"; echo "[ RUN ] >>> make $$t <<<"' \
'$(MAKE) --no-print-directory ROCKSDB_RECURSIVE_MAKE_SAME_CONFIG=1 "$$t"' \
'rc=$$?' \
'if [ "$$rc" = 0 ]; then' \
' echo "[ OK ] >>> make $$t <<<"' \
'else' \
' echo "[ FAILED ] >>> make $$t <<<"' \
'fi' \
'echo "[----------]"' \
'exit $$rc' \
> $$s; \
chmod a=rx $$s; \
fi; \
idx=$$((idx + 1)); \
done
# Reorder input lines (one per test or per shard) so the longest-running
# tests/shards start first under "make check" parallel scheduling. The trick
# is to prefix matched names with a fake duration (100), sort numerically
# descending, then strip the prefix. Slow-matched tests therefore come ahead
# of all unmatched tests; ordering within each group is unspecified.
# Reorder input lines (which are one per test) so that the
# longest-running tests appear first in the output.
# Do this by prefixing each selected name with its duration,
# sort the resulting names, and remove the leading numbers.
# FIXME: the "100" we prepend is a fake time, for now.
# FIXME: squirrel away timings from each run and use them
# (when present) on subsequent runs to order these tests.
#
# Why this matters: gnu_parallel hands out jobs in input order. If a slow
# test starts only after most others have finished, the run is bottlenecked
# on its tail. Front-loading slow tests overlaps them with the bulk of
# faster ones for much better wall-clock time.
# Without this reordering, these two tests would happen to start only
# after almost all other tests had completed, thus adding 100 seconds
# to the duration of parallel "make check". That's the difference
# between 4 minutes (old) and 2m20s (new).
#
# Maintenance:
# 1) Run `make check` so LOG is up-to-date.
# 2) Run `make suggest-slow-tests` to see candidate slow binaries.
# 3) Add binaries with high per-shard time (slow critical path) or high
# total time across many shards (need early queueing for fan-out).
# Each alternative is wrapped in `^.*NAME.*$$` so the *whole input line* is
# captured into $$1; then `s,(...),100 $$1,` prepends "100 " to the line.
# 152.120 PASS t/DBTest.FileCreationRandomFailure
# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
#
# Tiers below are based on observed timings (see suggest-slow-tests).
# Tier 1: max single-shard time >= 30s (critical-path bottlenecks).
# Tier 2: max single-shard time 15-30s.
# Tier 3: huge total time across many tiny shards; front-loading them keeps
# the tail of the run busy while big shards finish.
# With sharded test execution, prioritize binaries known to be slow.
# These generate many shards and should start early for good load balancing.
slow_test_regexp = \
^.*point_lock_manager_stress_test.*$$|^.*db_test.*$$|^.*external_sst_file_test.*$$|^.*compaction_service_test.*$$|^.*corruption_test.*$$|^.*comparator_db_test.*$$|^.*external_sst_file_basic_test.*$$|^.*rate_limiter_test.*$$|^.*db_compaction_test.*$$|^.*write_prepared_transaction_test.*$$|^.*db_merge_operator_test.*$$|\
^.*db_dynamic_level_test.*$$|^.*db_bloom_filter_test.*$$|^.*error_handler_fs_test.*$$|^.*merge_helper_test.*$$|^.*transaction_test.*$$|^.*db_kv_checksum_test.*$$|^.*inlineskiplist_test.*$$|\
^.*db_with_timestamp_basic_test.*$$|^.*table_test.*$$|^.*db_wal_test.*$$|^.*block_based_table_reader_test.*$$|^.*block_test.*$$|\
^.*run-check-c-api-gen.*$$|^.*run-check-format.*$$
^.*block_based_table_reader_test.*$$|^.*table_test.*$$|^.*block_test.*$$|^.*write_prepared_transaction_test.*$$|^.*transaction_test.*$$|^.*external_sst_file_test.*$$|^.*db_wal_test.*$$|^.*db_with_timestamp_basic_test.*$$|^.*db_test-.*$$
prioritize_long_running_tests = \
perl -pe 's,($(slow_test_regexp)),100 $$1,' \
| sort -k1,1gr \
| sed 's/^[.0-9]* //'
# Helper: print binaries observed to be slow in the last `make check` run.
# Use this to decide whether `slow_test_regexp` above needs updating.
# Aggregates per-binary across shards; flags any binary whose max single-shard
# time is >= 20s OR whose total time is >= 200s.
.PHONY: suggest-slow-tests
suggest-slow-tests:
@if [ ! -s LOG ]; then \
echo "No (or empty) LOG file. Run 'make check' first." >&2; exit 1; \
fi
@bash -c '$(quoted_perl_command)' < LOG | awk '\
{ \
t=$$1; name=$$3; \
sub(/^t\/run-/, "", name); \
sub(/-shard-[0-9]+$$/, "", name); \
total[name] += t; \
if (t > max[name]) max[name] = t; \
count[name]++; \
} \
END { \
printf "%8s %8s %5s %s\n", "TOTAL_s", "MAX_s", "SHRDS", "BINARY"; \
for (n in total) \
if (max[n] >= 20 || total[n] >= 200) \
printf "%8.1f %8.1f %5d %s\n", total[n], max[n], count[n], n; \
}' | (read header; echo "$$header"; sort -k2,2gr)
# "make check" uses
# Run with "make J=1 check" to disable parallelism in "make check".
# Run with "make J=200% check" to run two parallel jobs per core.
@@ -1108,7 +990,7 @@ check_0:
awk -v s=$(CI_SHARD_INDEX) -v n=$(CI_TOTAL_SHARDS) '(NR-1)%n==s'; \
else cat; fi; \
fi; \
$(FIND) t -name 'run-*' -print; \
find t -name 'run-*' -print; \
} \
| $(prioritize_long_running_tests) \
| grep -E '$(tests-regexp)' \
@@ -1130,7 +1012,7 @@ valgrind_check_0:
' run "make watch-log" in a separate window' ''; \
{ \
printf './%s\n' $(filter-out $(PARALLEL_TEST) %skiplist_test options_settable_test, $(TESTS)); \
$(FIND) t -name 'run-*' -print; \
find t -name 'run-*' -print; \
} \
| $(prioritize_long_running_tests) \
| grep -E '$(tests-regexp)' \
@@ -1173,110 +1055,26 @@ check: all
&& (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
grep -q 'GNU Parallel'; \
then \
$(MAKE) gen_check_scripts; \
$(MAKE) T="$$t" check_0; \
else \
for t in $(TESTS); do \
echo "===== Running $$t (`date`)"; ./$$t || exit 1; done; \
for t in $(CHECK_EXTRA_TARGETS); do \
echo "===== Running $$t (`date`)"; $(MAKE) $$t || exit 1; done; \
fi
rm -rf $(TEST_TMPDIR)
# NOTE: ^^^^^^^^ don't add new auxiliary checks here, but instead to
# CHECK_EXTRA_TARGETS
# Check that the auto-generated C API files are up to date. It regenerates the
# fragments and the inlined c.h/c.cc and compares them against a snapshot of the
# checked-in copies (no net change when everything is up to date). It requires
# clang++ (libclang, used to parse the C++ headers) and clang-format. When those
# are unavailable the staleness/compat sub-checks are SKIPPED with a message so
# `make check` still works without the codegen toolchain; the link-completeness
# sub-check always runs (it needs no toolchain).
#
# Pin the formatter to match CI by setting CLANG_FORMAT_BINARY, e.g.:
# make check-c-api-gen CLANG_FORMAT_BINARY=clang-format-21
# This target is part of `make check` and is skipped by SKIP_FORMAT_BUCK_CHECKS.
#
# Set CHECK_C_API_GEN_STRICT=1 to turn every "skip" below into a hard error, so a
# core CI job that runs this target cannot silently regress to a no-op if a
# prerequisite (clang++, or the compat baseline ref) goes missing. The dedicated
# build-linux-clang-21-no_test_run CI job runs this target with the flag set.
# Any non-empty value other than 0/no/false enables strict mode.
CLANG_FORMAT_BINARY ?=
# Backward-compatibility baseline for the C API (signature-level) check. CI
# overrides this with the PR's merge target or the branch's pre-push tip;
# locally it falls back to main or origin/main and is skipped if neither resolves.
API_COMPAT_REF ?= main
CHECK_C_API_GEN_STRICT ?=
check-c-api-gen:
# Link-completeness is a property of the checked-in c.h/c.cc and needs no
# clang toolchain, so it always runs: every declared public C API function
# must have exactly one definition (guards against dropped wrappers that
# would break downstream language bindings at link time).
$(PYTHON) tools/c_api_gen/check_api_completeness.py
# Backward-compatibility: no public C function may be removed or have its
# signature changed vs the baseline. Skipped if the baseline ref is not
# resolvable locally (CI passes an explicit ref), unless CHECK_C_API_GEN_STRICT.
@strict=""; case "$(CHECK_C_API_GEN_STRICT)" in ""|0|no|NO|false|FALSE) ;; *) strict=1 ;; esac; \
ref=""; \
if git rev-parse --verify --quiet "$(API_COMPAT_REF)^{commit}" >/dev/null; then ref="$(API_COMPAT_REF)"; \
elif git rev-parse --verify --quiet "origin/$(API_COMPAT_REF)^{commit}" >/dev/null; then ref="origin/$(API_COMPAT_REF)"; fi; \
if [ -n "$$ref" ]; then \
$(PYTHON) tools/c_api_gen/check_api_compatibility.py --ref "$$ref"; \
elif [ -n "$$strict" ]; then \
echo "ERROR: C API compat baseline '$(API_COMPAT_REF)' not resolvable and CHECK_C_API_GEN_STRICT is set" >&2; exit 1; \
else \
echo "Skipping C API backward-compatibility check ($(API_COMPAT_REF) not found; set API_COMPAT_REF)"; \
fi
# Staleness: regenerate and confirm the checked-in output is current. Needs a
# clang++ (the generator parses C++ ASTs). Use CLANG_CXX, which
# build_tools/build_detect_platform resolves once (the fbcode clang++ in
# fbcode builds, else a versioned clang++ on PATH) and passes explicitly, so
# the check and the generator agree on the same binary without heuristics.
@strict=""; case "$(CHECK_C_API_GEN_STRICT)" in ""|0|no|NO|false|FALSE) ;; *) strict=1 ;; esac; \
cf_arg=""; \
if [ -n "$(CLANG_FORMAT_BINARY)" ]; then cf_arg="--clang-format $(CLANG_FORMAT_BINARY)"; fi; \
if [ -n "$(CLANG_CXX)" ] && command -v "$(CLANG_CXX)" >/dev/null 2>&1; then \
$(PYTHON) tools/c_api_gen/verify_generated_up_to_date.py --clang "$(CLANG_CXX)" $$cf_arg; \
elif [ -n "$$strict" ]; then \
echo "ERROR: no clang++ found (CLANG_CXX unset) and CHECK_C_API_GEN_STRICT is set; cannot run the C API staleness check" >&2; exit 1; \
else \
echo "Skipping C API codegen staleness check (no clang++ found; CLANG_CXX unset, install clang++ to enable)"; \
fi
# Regenerate the checked-in C API generated files (fragments under c_api_gen/,
# the inlined include/rocksdb/c.h and db/c.cc, and the round-trip tests). Uses
# CLANG_CXX (resolved by build_tools/build_detect_platform) so it works in a gcc
# build too. Aborts up front if no usable clang++ is available, so a bad
# toolchain never leaves the tree in a partially regenerated state. Pin the
# formatter to match CI with CLANG_FORMAT_BINARY, e.g.:
# make regen-c-api CLANG_FORMAT_BINARY=clang-format-21
.PHONY: regen-c-api
regen-c-api:
@if [ -z "$(CLANG_CXX)" ] || ! command -v "$(CLANG_CXX)" >/dev/null 2>&1; then \
echo "ERROR: no clang++ found (CLANG_CXX unset); build on a host with clang++ or the fbcode toolchain to regenerate the C API" >&2; exit 1; \
fi
@cf_arg=""; \
if [ -n "$(CLANG_FORMAT_BINARY)" ]; then cf_arg="--clang-format $(CLANG_FORMAT_BINARY)"; fi; \
$(PYTHON) tools/c_api_gen/regen_all.py --clang "$(CLANG_CXX)" $$cf_arg
# Quick local validation for C API generation plus the focused C API test.
# This verifies the checked-in generated fragments as well as the inlined
# include/rocksdb/c.h and db/c.cc outputs, then runs c_test in an isolated
# TEST_TMPDIR to avoid stale-state failures.
check-c-api-c_test:
$(PYTHON) tools/c_api_gen/verify_generated_up_to_date.py --clang "$(CLANG_CXX)"
$(MAKE) c_test
@tmpdir=$$(mktemp -d); \
trap 'rm -rf "$$tmpdir"' EXIT; \
echo "===== Running c_test with TEST_TMPDIR=$$tmpdir"; \
if command -v timeout >/dev/null 2>&1; then \
TEST_TMPDIR="$$tmpdir" timeout 60 ./c_test; \
elif command -v gtimeout >/dev/null 2>&1; then \
TEST_TMPDIR="$$tmpdir" gtimeout 60 ./c_test; \
else \
TEST_TMPDIR="$$tmpdir" ./c_test; \
fi
ifneq ($(PLATFORM), OS_AIX)
$(PYTHON) tools/check_all_python.py
ifndef ASSERT_STATUS_CHECKED # not yet working with these tests
$(PYTHON) tools/ldb_test.py
$(PYTHON) tools/db_crashtest_test.py
sh tools/rocksdb_dump_test.sh
endif
endif
ifndef SKIP_FORMAT_BUCK_CHECKS
$(MAKE) check-format
$(MAKE) check-buck-targets
$(MAKE) check-sources
$(MAKE) check-workflow-yaml
endif
# TODO add ldb_tests
check_some: $(ROCKSDBTESTS_SUBSET)
@@ -1290,14 +1088,6 @@ ldb_tests: ldb
db_crashtest_tests:
$(PYTHON) tools/db_crashtest_test.py
.PHONY: check-all-python
check-all-python:
$(PYTHON) tools/check_all_python.py
.PHONY: rocksdb_dump_test
rocksdb_dump_test: rocksdb_dump rocksdb_undump
sh tools/rocksdb_dump_test.sh
include crash_test.mk
asan_check: clean
@@ -1435,32 +1225,16 @@ rocksdb.h rocksdb.cc: build_tools/amalgamate.py Makefile $(LIB_SOURCES) unity.cc
build_tools/amalgamate.py -I. -i./include unity.cc -x include/rocksdb/c.h -H rocksdb.h -o rocksdb.cc
clean: clean-ext-libraries-all clean-rocks clean-rocksjava
# Removed here rather than in clean-rocks (via CLEAN_FILES) so the build-parameter
# auto-clean, which runs clean-rocks, doesn't delete the make_config.mk already
# included by the in-progress make.
@rm -f make_config.mk
clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-rocksjava
@rm -f make_config.mk
clean-rocks:
# Not practical to exactly match all versions/variants in naming (e.g. debug or not)
@echo Removing link targets
@rm -f ${LIBNAME}*.so* ${LIBNAME}*.a $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(MICROBENCHS)
@echo Finding and cleaning other files
@rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
@if $(FIND) Makefile -regextype awk &> /dev/null; then \
$(FIND) . -regextype awk \
-regex '.*/([.].*|third-party)' -prune -o \
-type f -regex '.*[.]([oda]|gcda|gcno)' -exec rm -f {} \; ; \
else \
$(FIND) . -name "*.[oda]" -exec rm -f {} \; ; \
fi
# Remove the build signature(s) LAST. Done after the object files are gone so
# that a Ctrl+C partway through clean leaves the old signature in place: it
# still matches the leftover objects, so a later build with different
# parameters is still detected (signature usefulness is preserved).
@rm -f $(BUILD_SIG_FILE) jl/.build_signature jls/.build_signature
rm -f ${LIBNAME}*.so* ${LIBNAME}*.a
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(MICROBENCHS)
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
$(FIND) . -type f \( -name "*.gcda" -o -name "*.gcno" \) -exec rm -f {} \;
clean-rocksjava: clean-rocks
rm -rf jl jls
@@ -1473,7 +1247,7 @@ clean-ext-libraries-all:
rm -rf bzip2* snappy* zlib* lz4* zstd*
clean-ext-libraries-bin:
$(FIND) . -maxdepth 1 -type d \( -name bzip2\* -or -name snappy\* -or -name zlib\* -or -name lz4\* -or -name zstd\* \) -prune -exec rm -rf {} \;
find . -maxdepth 1 -type d \( -name bzip2\* -or -name snappy\* -or -name zlib\* -or -name lz4\* -or -name zstd\* \) -prune -exec rm -rf {} \;
tags:
ctags -R .
@@ -1733,9 +1507,6 @@ db_readonly_with_timestamp_test: $(OBJ_DIR)/db/db_readonly_with_timestamp_test.o
db_wide_basic_test: $(OBJ_DIR)/db/wide/db_wide_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_lazy_entity_test: $(OBJ_DIR)/db/wide/db_lazy_entity_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_wide_blob_direct_write_test: $(OBJ_DIR)/db/wide/db_wide_blob_direct_write_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -1754,7 +1525,7 @@ db_open_with_config_test: $(OBJ_DIR)/db/db_open_with_config_test.o $(TEST_LIBRAR
db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_etc2_test: $(OBJ_DIR)/db/db_etc2_test.o $(TEST_LIBRARY) $(LIBRARY)
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_etc3_test: $(OBJ_DIR)/db/db_etc3_test.o $(TEST_LIBRARY) $(LIBRARY)
@@ -1895,9 +1666,6 @@ backup_engine_test: $(OBJ_DIR)/utilities/backup/backup_engine_test.o $(TEST_LIBR
checkpoint_test: $(OBJ_DIR)/utilities/checkpoint/checkpoint_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
copy_engine_test: $(OBJ_DIR)/utilities/copy_engine/copy_engine_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
sorted_run_builder_test: $(OBJ_DIR)/utilities/sorted_run_builder/sorted_run_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2018,9 +1786,6 @@ block_fetcher_test: table/block_fetcher_test.o $(TEST_LIBRARY) $(LIBRARY)
block_test: $(OBJ_DIR)/table/block_based/block_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
builtin_index_factory_test: $(OBJ_DIR)/table/block_based/builtin_index_factory_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
data_block_hash_index_test: $(OBJ_DIR)/table/block_based/data_block_hash_index_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2443,27 +2208,22 @@ ROCKSDB_JAVADOCS_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-javadoc.jar
ROCKSDB_SOURCES_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-sources.jar
SHA256_CMD = sha256sum
# The *_DOWNLOAD_PREFIX defaults below point at the upstream projects; CI
# overrides them to a mirror on the "third-party-deps" GitHub release (see
# .github/actions/pre-steps/action.yml). When bumping a version, see
# instructions in CI config comments for how to update that release.
ZLIB_VER ?= 1.3.1
ZLIB_SHA256 ?= 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23
ZLIB_DOWNLOAD_PREFIX ?= http://zlib.net/zlib-
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
BZIP2_VER ?= 1.0.8
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
BZIP2_DOWNLOAD_PREFIX ?= http://sourceware.org/pub/bzip2/bzip2-
BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
SNAPPY_VER ?= 1.2.2
SNAPPY_SHA256 ?= 90f74bc1fbf78a6c56b3c4a082a05103b3a56bb17bca1a27e052ea11723292dc
SNAPPY_DOWNLOAD_PREFIX ?= https://github.com/google/snappy/archive/
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
LZ4_VER ?= 1.10.0
LZ4_SHA256 ?= 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b
LZ4_DOWNLOAD_PREFIX ?= https://github.com/lz4/lz4/archive/v
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
ZSTD_VER ?= 1.5.7
ZSTD_SHA256 ?= 37d7284556b20954e56e1ca85b80226768902e2edabd3b649e9e72c0c9012ee3
ZSTD_DOWNLOAD_PREFIX ?= https://github.com/facebook/zstd/archive/v
# curl with the common options used to fetch the pinned third-party sources above
CURL_DOWNLOAD = curl --fail --location --retry 3 --retry-all-errors
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
CURL_SSL_OPTS ?= --tlsv1
ifeq ($(PLATFORM), OS_MACOSX)
ifeq (,$(findstring librocksdbjni-osx,$(ROCKSDBJNILIB)))
@@ -2508,103 +2268,8 @@ ifeq ($(PLATFORM), OS_OPENBSD)
endif
export SHA256_CMD
# ----------------------------------------------------------------------------
# Build parameter change detection
# ----------------------------------------------------------------------------
# RocksDB writes object files to the same $(OBJ_DIR) paths regardless of
# DEBUG_LEVEL, sanitizers (ASAN/TSAN/UBSAN), ASSERT_STATUS_CHECKED, RTTI, LTO,
# COERCE_CONTEXT_SWITCH, etc. Most of those are applied in this Makefile after
# `include make_config.mk` and are therefore NOT reflected in make_config.mk,
# so switching them and rebuilding silently mixes incompatible object files.
# To guard against that, we hash the fully-resolved compile/link parameters
# (which already embed make_config.mk via PLATFORM_*) and compare against the
# value recorded from the previous build. On mismatch the build stops so the
# user can `make clean`. Knobs:
# AUTO_CLEAN=1
# run `make clean-rocks` automatically on mismatch
# ALLOW_BUILD_PARAMETER_CHANGE=1
# skip the check entirely (e.g. intentionally mixing DEBUG_LEVEL=1 and
# DEBUG_LEVEL=2)
# The signature is stored per-$(OBJ_DIR), so Java (jl/jls) builds are tracked
# independently from the default build.
BUILD_SIG_FILE := $(OBJ_DIR)/.build_signature
# NOTE: deliberately NOT added to CLEAN_FILES. The signature is removed as the
# very last step of clean-rocks so that an interrupted clean keeps it (see
# clean-rocks), preserving change detection across a Ctrl+C'd clean.
# Goals that do not compile object files into the tree (pure utilities,
# informational, source/config checks): the change check is irrelevant for them.
BUILD_SIG_NONBUILD_GOALS := \
clean clean-rocks clean-not-downloaded clean-rocksjava \
clean-not-downloaded-rocksjava clean-ext-libraries-all \
clean-ext-libraries-bin \
format format-auto check-format check-buck-targets check-headers \
check-sources check-workflow-yaml check-progress clang-tidy \
tags tags0 package jclean checkout_folly build_folly \
watch-log dump-log suggest-slow-tests list_all_tests gen-pc \
gen_parallel_tests gen_check_scripts check-c-api-gen check-all-python \
setup-hooks install-hooks uninstall-hooks uninstall db_crashtest_tests
# Goals that clean before building (depend on or invoke `clean`): they manage
# their own freshness, so the check must not block them (it would error before
# their built-in clean runs).
BUILD_SIG_NONBUILD_GOALS += \
release coverage build_size analyze analyze_incremental \
asan_check asan_crash_test asan_crash_test_with_atomic_flush \
asan_crash_test_with_txn asan_crash_test_with_best_efforts_recovery \
whitebox_asan_crash_test blackbox_asan_crash_test \
ubsan_check ubsan_crash_test ubsan_crash_test_with_atomic_flush \
ubsan_crash_test_with_txn ubsan_crash_test_with_best_efforts_recovery \
whitebox_ubsan_crash_test blackbox_ubsan_crash_test
# Goals that explicitly clean; never trip the check when one is requested.
BUILD_SIG_CLEAN_GOALS := \
clean clean-rocks clean-not-downloaded clean-rocksjava \
clean-not-downloaded-rocksjava clean-ext-libraries-all \
clean-ext-libraries-bin
BUILD_SIG_GOALS := $(if $(MAKECMDGOALS),$(MAKECMDGOALS),all)
BUILD_SIG_DO_BUILD := $(filter-out $(BUILD_SIG_NONBUILD_GOALS),$(BUILD_SIG_GOALS))
BUILD_SIG_CLEANING := $(filter $(BUILD_SIG_CLEAN_GOALS),$(MAKECMDGOALS))
# Dry runs (-n/--just-print) must not write the stamp, clean, or error; the
# parse-time $(shell) below would otherwise run even under -n. Note that
# actual options are canonicalized and shorted as possible such that all
# short options are in the first word of MAKEFLAGS.
BUILD_SIG_DRYRUN := $(findstring n,$(firstword -$(MAKEFLAGS)))
# Only enforce at the top level. Sub-makes (e.g. `check` invokes
# $(MAKE) gen_parallel_tests / check-c-api-gen / check_0) inherit the parent's
# build flags, so the top-level check already covers them; re-checking inside a
# sub-make only causes spurious failures.
ifneq ($(ALLOW_BUILD_PARAMETER_CHANGE),1)
ifeq ($(MAKELEVEL),0)
ifeq ($(BUILD_SIG_DRYRUN),)
ifeq ($(BUILD_SIG_CLEANING),)
ifneq ($(BUILD_SIG_DO_BUILD),)
BUILD_SIG := $(shell printf '%s' '$(CC)|$(CXX)|$(CFLAGS)|$(CXXFLAGS)|$(LDFLAGS)|$(EXEC_LDFLAGS)' | $(SHA256_CMD) | cut -d ' ' -f 1)
BUILD_SIG_OLD := $(shell cat $(BUILD_SIG_FILE) 2>/dev/null)
ifneq ($(BUILD_SIG_OLD),)
ifneq ($(BUILD_SIG_OLD),$(BUILD_SIG))
ifeq ($(AUTO_CLEAN),1)
$(info *** Build parameters changed since last build (OBJ_DIR=$(OBJ_DIR)); running 'make clean-rocks' because AUTO_CLEAN=1)
BUILD_SIG_CLEAN_OUTPUT := $(shell $(MAKE) clean-rocks 1>&2)
ifneq ($(.SHELLSTATUS),0)
$(error AUTO_CLEAN: 'make clean-rocks' failed (exit $(.SHELLSTATUS)); not building against a partially-cleaned tree)
endif
else
$(error Build parameters changed since the last build (OBJ_DIR=$(OBJ_DIR)). Existing object files are stale and must be removed. Run 'make clean', or set AUTO_CLEAN=1 to clean automatically, or ALLOW_BUILD_PARAMETER_CHANGE=1 to build anyway)
endif
endif
endif
# Record the current signature for the next build.
BUILD_SIG_WRITE := $(shell mkdir -p $(OBJ_DIR) 2>/dev/null; printf '%s\n' '$(BUILD_SIG)' > $(BUILD_SIG_FILE))
endif
endif
endif
endif
endif
zlib-$(ZLIB_VER).tar.gz:
$(CURL_DOWNLOAD) --output zlib-$(ZLIB_VER).tar.gz ${ZLIB_DOWNLOAD_PREFIX}$(ZLIB_VER).tar.gz
curl --fail --output zlib-$(ZLIB_VER).tar.gz --location ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \
if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \
echo zlib-$(ZLIB_VER).tar.gz checksum mismatch, expected=\"$(ZLIB_SHA256)\" actual=\"$$ZLIB_SHA256_ACTUAL\"; \
@@ -2622,7 +2287,7 @@ libz.a: zlib-$(ZLIB_VER).tar.gz
cp zlib-$(ZLIB_VER)/libz.a .
bzip2-$(BZIP2_VER).tar.gz:
$(CURL_DOWNLOAD) --output bzip2-$(BZIP2_VER).tar.gz ${BZIP2_DOWNLOAD_PREFIX}$(BZIP2_VER).tar.gz
curl --fail --output bzip2-$(BZIP2_VER).tar.gz --location ${CURL_SSL_OPTS} ${BZIP2_DOWNLOAD_BASE}/bzip2-$(BZIP2_VER).tar.gz
BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \
if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \
echo bzip2-$(BZIP2_VER).tar.gz checksum mismatch, expected=\"$(BZIP2_SHA256)\" actual=\"$$BZIP2_SHA256_ACTUAL\"; \
@@ -2636,7 +2301,7 @@ libbz2.a: bzip2-$(BZIP2_VER).tar.gz
cp bzip2-$(BZIP2_VER)/libbz2.a .
snappy-$(SNAPPY_VER).tar.gz:
$(CURL_DOWNLOAD) --output snappy-$(SNAPPY_VER).tar.gz ${SNAPPY_DOWNLOAD_PREFIX}$(SNAPPY_VER).tar.gz
curl --fail --output snappy-$(SNAPPY_VER).tar.gz --location ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \
@@ -2651,7 +2316,7 @@ libsnappy.a: snappy-$(SNAPPY_VER).tar.gz
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
lz4-$(LZ4_VER).tar.gz:
$(CURL_DOWNLOAD) --output lz4-$(LZ4_VER).tar.gz ${LZ4_DOWNLOAD_PREFIX}$(LZ4_VER).tar.gz
curl --fail --output lz4-$(LZ4_VER).tar.gz --location ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
echo lz4-$(LZ4_VER).tar.gz checksum mismatch, expected=\"$(LZ4_SHA256)\" actual=\"$$LZ4_SHA256_ACTUAL\"; \
@@ -2665,7 +2330,7 @@ liblz4.a: lz4-$(LZ4_VER).tar.gz
cp lz4-$(LZ4_VER)/lib/liblz4.a .
zstd-$(ZSTD_VER).tar.gz:
$(CURL_DOWNLOAD) --output zstd-$(ZSTD_VER).tar.gz ${ZSTD_DOWNLOAD_PREFIX}$(ZSTD_VER).tar.gz
curl --fail --output zstd-$(ZSTD_VER).tar.gz --location ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
echo zstd-$(ZSTD_VER).tar.gz checksum mismatch, expected=\"$(ZSTD_SHA256)\" actual=\"$$ZSTD_SHA256_ACTUAL\"; \
-11
View File
@@ -145,21 +145,11 @@ def generate_buck(repo_path, deps_map):
# always add range_tree, it's only excluded on ppc64, which we don't use internally
src_mk["RANGE_TREE_SOURCES"] + src_mk["TOOL_LIB_SOURCES"],
deps=[
"//folly:executor",
"//folly/container:f14_hash",
"//folly/coro:baton",
"//folly/coro:blocking_wait",
"//folly/coro:collect",
"//folly/coro:coroutine",
"//folly/coro:nothrow",
"//folly/coro:task",
"//folly/executors:io_executor",
"//folly/executors:io_thread_pool_executor",
"//folly/executors/thread_factory:named_thread_factory",
"//folly/io/async:async_base",
"//folly/io/async:event_base_manager",
"//folly/io/async:io_uring_backend",
"//folly/io/async:request_context",
"//folly/synchronization:distributed_mutex",
],
headers=LiteralValue("glob([\"**/*.h\"])")
@@ -370,7 +360,6 @@ def generate_buck(repo_path, deps_map):
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
)
BUCK.export_file("tools/db_crashtest.py")
BUCK.export_file("tools/fault_injection_log_parser.py")
print(ColorString.info("Generated BUCK Summary:"))
print(ColorString.info("- %d libs" % BUCK.total_lib))
-6
View File
@@ -118,12 +118,6 @@ class TARGETSBuilder:
self.total_bin = self.total_bin + 1
def add_c_test(self):
# The actual c_test_bin target is defined by add_c_test_wrapper in the
# internal //rocks/buckifier:defs.bzl (not in this OSS repo). db/c_test.c
# #includes the generated c_api_gen/*.inc fragments, so under Buck's
# hermetic sandbox that wrapper must expose them as headers, e.g.
# headers = native.glob(["c_api_gen/**/*.inc"])
# (Make/CMake resolve the include via -I. / PROJECT_SOURCE_DIR.)
with open(self.path, "ab") as targets_file:
targets_file.write(
b"""
+19 -43
View File
@@ -27,6 +27,7 @@
# -DLZ4 if the LZ4 library is present
# -DZSTD if the ZSTD library is present
# -DNUMA if the NUMA library is present
# -DTBB if the TBB library is present
# -DMEMKIND if the memkind library is present
#
# Using gflags in rocksdb:
@@ -89,36 +90,13 @@ if test -z "$CC"; then
fi
fi
# CLANG_CXX is the single clang++ detection point for the whole script: it is
# used for CXX when building with clang, for CLANG_ANALYZER (scan-build), and by
# tooling that needs clang even in a gcc build (e.g. the C API codegen, which
# parses C++ ASTs). The fbcode config above already sets CLANG_CXX; otherwise
# resolve it here.
if test -z "$CLANG_CXX"; then
if [ -n "$CXX" ] && echo | $CXX -dM -E -x c++ - 2>/dev/null | grep -q '\b__clang__\b'; then
# Honor an already-configured clang compiler (e.g. CI sets
# CXX=clang++-21) so codegen parses headers with the same clang the
# build uses, not an unrelated clang++ that merely happens to come first
# on PATH (an image may ship both a bare clang++ and clang++-NN).
CLANG_CXX="$CXX"
else
# Otherwise take the first clang++ on PATH. Candidates cover a bare
# clang++, versioned names, and clang++.par (a Meta-internal wrapper
# often present on dev machines).
CLANG_CXX=$(command -v clang++ || command -v clang++-21 || command -v clang++-20 \
|| command -v clang++-19 || command -v clang++-18 || command -v clang++-17 \
|| command -v clang++-16 || command -v clang++-15 || command -v clang++-14 \
|| command -v clang++-13 || command -v clang++.par || true)
fi
fi
if test -z "$CXX"; then
if [ "$USE_CLANG" ] && [ -n "$CLANG_CXX" ]; then
CXX="$CLANG_CXX"
if [ "$USE_CLANG" -a -x "$(command -v clang++)" ]; then
CXX=clang++
elif [ -x "$(command -v g++)" ]; then
CXX=g++
elif [ -n "$CLANG_CXX" ]; then
CXX="$CLANG_CXX"
elif [ -x "$(command -v clang++)" ]; then
CXX=clang++
else
CXX=g++
fi
@@ -136,17 +114,6 @@ if test -z "$AR"; then
fi
fi
# If the selected C++ compiler is actually clang (e.g. clang was the only
# compiler available), reflect that as USE_CLANG so clang-specific logic in the
# Makefile applies consistently. Detect it via the compiler's own __clang__
# predefined macro rather than matching the binary path, which can false-positive
# (e.g. a home directory containing "clang").
if test -z "$USE_CLANG"; then
if echo | $CXX -dM -E -x c++ - 2>/dev/null | grep -q '\b__clang__\b'; then
USE_CLANG=1
fi
fi
# Detect OS
if test -z "$TARGET_OS"; then
TARGET_OS=`uname -s`
@@ -161,7 +128,7 @@ if test -z "$CLANG_SCAN_BUILD"; then
fi
if test -z "$CLANG_ANALYZER"; then
CLANG_ANALYZER="$CLANG_CXX"
CLANG_ANALYZER=$(command -v clang++ 2> /dev/null)
fi
if test -z "$FIND"; then
@@ -457,6 +424,19 @@ EOF
fi
fi
if ! test $ROCKSDB_DISABLE_TBB; then
# Test whether tbb is available
$CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -ltbb 2>/dev/null <<EOF
#include <tbb/tbb.h>
int main() {}
EOF
if [ "$?" = 0 ]; then
COMMON_FLAGS="$COMMON_FLAGS -DTBB"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb"
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb"
fi
fi
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
# Test whether jemalloc is available
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -ljemalloc \
@@ -852,10 +832,6 @@ echo "ROCKSDB_MINOR=$ROCKSDB_MINOR" >> "$OUTPUT"
echo "ROCKSDB_PATCH=$ROCKSDB_PATCH" >> "$OUTPUT"
echo "CLANG_SCAN_BUILD=$CLANG_SCAN_BUILD" >> "$OUTPUT"
echo "CLANG_ANALYZER=$CLANG_ANALYZER" >> "$OUTPUT"
echo "CLANG_CXX=$CLANG_CXX" >> "$OUTPUT"
if test -n "$USE_CLANG"; then
echo "USE_CLANG=$USE_CLANG" >> "$OUTPUT"
fi
echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT"
echo "FIND=$FIND" >> "$OUTPUT"
echo "WATCH=$WATCH" >> "$OUTPUT"
+1 -4
View File
@@ -1,8 +1,5 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the
# COPYING file in the root directory) and Apache 2.0 License
# (found in the LICENSE.Apache file in the root directory).
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# Check for some simple mistakes in public headers (on the command line)
# that should prevent commit or push
+15 -14
View File
@@ -1,20 +1,21 @@
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
# The file is generated using update_dependencies.sh.
GCC_BASE=/mnt/gvfs/third-party2/gcc/e9cb2d4550b5a95024125084420342d256b349cd/11.x/centos9-native/3bed279
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/4e46e6967e15412702e51960c69790a78933856f/21/platform010/72a2ff8
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/63c5179edc90fec473e53c1b2f92269c65c39e07/11.x/platform010/5684a5a
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/b4a6248a76a163bcccb1e4751e247712c535846e/2.34/platform010/f259413
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/ef895164499fdfedb9d9b9c85520de10aa8ff9ca/1.1.8/platform010/76ebdda
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/643dbf0136f869562485cfcb2953f5cab60447ef/1.2.8/platform010/76ebdda
GCC_BASE=/mnt/gvfs/third-party2/gcc/62de5a92e5f23c661c3d4b9f322e04eb14e7a5bd/11.x/centos8-native/886b5eb
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/1f6edd1ff15c99c861afc8f3cd69054cd974dd64/15/platform010/72a2ff8
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d1129753c8361ac8e9453c0f4291337a4507ebe6/11.x/platform010/5684a5a
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/fed6e93d87571fb162734c86636119d45a398963/2.34/platform010/f259413
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/31a346126a1f3b64812c362511cb04cc1bd40855/1.1.8/platform010/76ebdda
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/0c65c05468b5a38cef1a106a1f526463e120c8dd/1.2.8/platform010/76ebdda
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/09703139cfc376bd8a82642385a0e97726b28287/1.0.6/platform010/76ebdda
LZ4_BASE=/mnt/gvfs/third-party2/lz4/823bf8e000654abef784311296ffc8166d9d3986/1.9.4/platform010/76ebdda
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/0cda78647b5712bec7ec6d9ba2f0dcdabfb6f44f/1.4.x/platform010/64091f4
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/a02e0de00945729ad553c187c64e86b57f45a33b/2.2.0/platform010/76ebdda
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/a1a50742bec7195bfafb594e174ea45dc197a420/master/platform010/779a252
LZ4_BASE=/mnt/gvfs/third-party2/lz4/ff23d17b932725cc1734a14896a8b67c518ba169/1.9.4/platform010/76ebdda
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/576397d8b1d9cea7306ad1e454d5e55caaa2ff1c/1.4.x/platform010/64091f4
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/fecac07861cb829f5e60dbeff0503d3272db73c0/2.2.0/platform010/76ebdda
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/0bb3f5756788ce26e2e16a1cb2f2af2c59b51abe/master/platform010/f57cc4a
NUMA_BASE=/mnt/gvfs/third-party2/numa/5b602edd46fda54cdd7ea45f77dbe4061206e174/2.0.11/platform010/76ebdda
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/f3cce2e1a79ac8d64e102886603a6997d6e88640/1.8/platform010/76ebdda
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/97cac22a149c2e202917e05d44e87e516b68216f/1.4/platform010/5074a48
TBB_BASE=/mnt/gvfs/third-party2/tbb/53953ebc4e3eda85ad6fc3e429ba146035e97b90/2018_U5/platform010/76ebdda
LIBURING_BASE=/mnt/gvfs/third-party2/liburing/a98e2d137007e3ebf7f33bd6f99c2c56bdaf8488/20210212/platform010/76ebdda
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/780c7a0f9cf0967961e69ad08e61cddd85d61821/trunk/platform010/76ebdda
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/0883cccda758e68b47e4c04e4fa01142e1f60f32/fb/platform010/da39a3e
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/1d5d19135a406a941d3a441eb2bf506bb9d19210/2.43/centos9-native/be7139e
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/3e2b7ccc315c9a0f2b678ce6fd774f4970f9bf3b/3.22.0/platform010/76ebdda
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/624a2f8f6c93c3c1df8aa4a6255d8202631a6c80/fb/platform010/da39a3e
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/39579e8603b48b3540f8b0633f43adf29acccb8b/2.37/centos8-native/da39a3e
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/cd9cc656d49ecb53797ce4d055e49fde29fd57ff/3.19.0/platform010/76ebdda
+13 -4
View File
@@ -87,6 +87,15 @@ if test -z $PIC_BUILD; then
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
fi
# location of TBB
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
if test -z $PIC_BUILD; then
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
else
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
fi
CFLAGS+=" -DTBB"
test "$USE_SSE" || USE_SSE=1
export USE_SSE
test "$PORTABLE" || PORTABLE=1
@@ -95,7 +104,7 @@ export PORTABLE
BINUTILS="$BINUTILS_BASE/bin"
AR="$BINUTILS/ar"
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
STDLIBS="-L $GCC_BASE/lib64"
@@ -141,17 +150,17 @@ CFLAGS+=" $DEPS_INCLUDE"
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT"
CXXFLAGS+=" $CFLAGS"
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB"
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-5-glibc-2.23/lib/ld.so"
EXEC_LDFLAGS+=" $LIBUNWIND"
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-5-glibc-2.23/lib"
# dynamic loading
# required by libtbb
EXEC_LDFLAGS+=" -ldl"
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
VALGRIND_VER="$VALGRIND_BASE/bin/"
+10 -9
View File
@@ -82,6 +82,11 @@ CFLAGS+=" -DNUMA"
# location of libunwind
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind${MAYBE_PIC}.a"
# location of TBB
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
TBB_LIBS="$TBB_BASE/lib/libtbb${MAYBE_PIC}.a"
CFLAGS+=" -DTBB"
# location of LIBURING
LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/"
LIBURING_LIBS="$LIBURING_BASE/lib/liburing${MAYBE_PIC}.a"
@@ -96,7 +101,7 @@ BINUTILS="$BINUTILS_BASE/bin"
AR="$BINUTILS/ar"
AS="$BINUTILS/as"
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
STDLIBS="-L $GCC_BASE/lib64"
@@ -104,10 +109,6 @@ CLANG_BIN="$CLANG_BASE/bin"
CLANG_LIB="$CLANG_BASE/lib"
CLANG_SRC="$CLANG_BASE/../../src"
# A clang++ is always available in the fbcode toolchain regardless of whether
# the build itself uses gcc or clang. CLANG_CXX exposes it for tooling (e.g. the
# C API codegen, which parses C++ ASTs) that needs clang even in a gcc build.
CLANG_CXX="$CLANG_BIN/clang++"
CLANG_ANALYZER="$CLANG_BIN/clang++"
CLANG_SCAN_BUILD="$CLANG_BIN/scan-build"
@@ -156,19 +157,19 @@ CFLAGS+=" $DEPS_INCLUDE"
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_IOURING_PRESENT"
CXXFLAGS+=" $CFLAGS"
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $LIBURING_LIBS $BENCHMARK_LIBS"
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform010/lib/ld.so"
EXEC_LDFLAGS+=" $LIBUNWIND"
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform010/lib"
EXEC_LDFLAGS+=" -Wl,-rpath=$GCC_BASE/lib64"
# dynamic loading
# required by libtbb
EXEC_LDFLAGS+=" -ldl"
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
PLATFORM_LDFLAGS+=" -B$BINUTILS"
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
VALGRIND_VER="$VALGRIND_BASE/bin/"
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_CXX CLANG_ANALYZER CLANG_SCAN_BUILD
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
+73 -178
View File
@@ -1,188 +1,62 @@
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
"""
Pre-download packages with unreliable mirrors using fallback mirrors.
Reads package info from folly's getdeps manifest files.
"""
import configparser
import hashlib
import os
import shutil
import sys
import urllib.request
DOWNLOAD_TIMEOUT_SECONDS = 120
DOWNLOAD_CHUNK_BYTES = 64 * 1024
MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024
MIRROR_FALLBACKS = {
"ftpmirror.gnu.org/gnu/": [
"https://mirrors.kernel.org/gnu/",
"https://ftpmirror.gnu.org/gnu/",
"https://ftp.gnu.org/gnu/",
],
"ftp.gnu.org/gnu/": [
"https://mirrors.kernel.org/gnu/",
"https://ftpmirror.gnu.org/gnu/",
"https://ftp.gnu.org/gnu/",
],
}
# These packages must have URLs matching MIRROR_FALLBACKS; other packages are
# left for getdeps.py's normal download path.
PACKAGES_TO_CHECK = ("autoconf", "automake", "libtool", "libiberty")
import os
import hashlib
import subprocess
import configparser
def sha256_file(path):
"""Calculate SHA256 hash of a file."""
h = hashlib.sha256()
try:
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(65536), b''):
h.update(chunk)
return h.hexdigest()
except Exception:
return None
def parse_manifest(manifest_path):
"""Parse a getdeps manifest file to extract download info."""
# folly manifests can contain bare keys in sections unrelated to downloads.
config = configparser.ConfigParser(allow_no_value=True, interpolation=None)
config = configparser.ConfigParser()
try:
with open(manifest_path, encoding="utf-8") as manifest_file:
config.read_file(manifest_file)
except Exception as ex:
print(f" {os.path.basename(manifest_path)}: WARNING - parse failed: {ex}")
return None
if "download" in config:
return {
"url": config["download"].get("url", ""),
"sha256": config["download"].get("sha256", ""),
}
return None
def file_size(path):
try:
return os.path.getsize(path)
config.read(manifest_path)
if 'download' in config:
return {
'url': config['download'].get('url', ''),
'sha256': config['download'].get('sha256', ''),
}
except Exception:
return None
pass
return None
def get_fallback_mirrors(url):
"""Get fallback mirror URLs for a given URL."""
for pattern, mirrors in MIRROR_FALLBACKS.items():
# Fallback mirror patterns for known unreliable hosts
mirror_fallbacks = {
"ftp.gnu.org/gnu/": [
"https://mirrors.kernel.org/gnu/",
"https://ftpmirror.gnu.org/gnu/",
"https://ftp.gnu.org/gnu/",
],
"ftpmirror.gnu.org/gnu/": [
"https://mirrors.kernel.org/gnu/",
"https://ftpmirror.gnu.org/gnu/",
"https://ftp.gnu.org/gnu/",
],
}
for pattern, mirrors in mirror_fallbacks.items():
if pattern in url:
# Extract the path after the pattern
path_start = url.find(pattern) + len(pattern)
path = url[path_start:]
return [mirror + path for mirror in mirrors]
return []
def download_url(url, filepath):
"""Download URL to filepath without leaving partial files behind."""
tmp_filepath = filepath + ".tmp"
if os.path.exists(tmp_filepath):
os.remove(tmp_filepath)
request = urllib.request.Request(
url, headers={"User-Agent": "rocksdb-getdeps-fallback/1.0"}
)
try:
with urllib.request.urlopen(
request, timeout=DOWNLOAD_TIMEOUT_SECONDS
) as response, open(tmp_filepath, "wb") as output:
copied = 0
while True:
chunk = response.read(DOWNLOAD_CHUNK_BYTES)
if not chunk:
break
copied += len(chunk)
if copied > MAX_DOWNLOAD_BYTES:
raise Exception(
f"download exceeds {MAX_DOWNLOAD_BYTES} bytes"
)
output.write(chunk)
os.replace(tmp_filepath, filepath)
finally:
if os.path.exists(tmp_filepath):
os.remove(tmp_filepath)
def prepare_download(package, info, download_dir, cache_dir):
url = info["url"]
expected_sha256 = info["sha256"]
mirrors = get_fallback_mirrors(url)
if not mirrors:
return False
if not expected_sha256:
print(f" {package}: WARNING - skipped fallback without sha256")
return False
# getdeps uses format: {package}-{filename}
filename = f"{package}-{os.path.basename(url)}"
filepath = os.path.join(download_dir, filename)
cache_path = os.path.join(cache_dir, filename)
# Check if already valid.
actual_sha256 = sha256_file(filepath) if os.path.exists(filepath) else None
if actual_sha256 == expected_sha256:
print(f" {filename}: OK (already downloaded)")
return True
if actual_sha256 is not None:
print(
f" {filename}: WARNING - removing invalid download "
f"sha256={actual_sha256}"
)
os.remove(filepath)
# The cache is only an opportunistic single-build accelerator; callers
# should not share it across concurrent builds without external locking.
actual_sha256 = sha256_file(cache_path) if os.path.exists(cache_path) else None
if actual_sha256 == expected_sha256:
print(f" {filename}: OK (from cache)")
shutil.copy2(cache_path, filepath)
return True
if actual_sha256 is not None:
print(
f" {filename}: WARNING - removing invalid cache "
f"sha256={actual_sha256}"
)
os.remove(cache_path)
# Try fallback mirrors.
for mirror_url in mirrors:
print(f" {filename}: trying {mirror_url}...")
try:
download_url(mirror_url, filepath)
except Exception as ex:
print(f" {filename}: WARNING - download failed: {ex}")
continue
actual_sha256 = sha256_file(filepath)
if actual_sha256 == expected_sha256:
size = file_size(filepath)
print(f" {filename}: OK (downloaded, {size} bytes)")
shutil.copy2(filepath, cache_path)
return True
size = file_size(filepath)
print(
f" {filename}: WARNING - sha256 mismatch from {mirror_url}: "
f"expected={expected_sha256} actual={actual_sha256} size={size}"
)
os.remove(filepath)
print(f" {filename}: WARNING - all mirrors failed")
return False
return [url] # No fallback, use original
def main():
if len(sys.argv) != 4:
@@ -190,39 +64,60 @@ def main():
sys.exit(1)
download_dir, cache_dir, manifests_dir = sys.argv[1], sys.argv[2], sys.argv[3]
os.makedirs(download_dir, exist_ok=True)
os.makedirs(cache_dir, exist_ok=True)
checked = 0
ready = 0
for package in PACKAGES_TO_CHECK:
# Packages known to have unreliable mirrors
packages_to_check = ["autoconf", "automake", "libtool"]
for package in packages_to_check:
manifest_path = os.path.join(manifests_dir, package)
if not os.path.isfile(manifest_path):
if not os.path.exists(manifest_path):
continue
info = parse_manifest(manifest_path)
if not info or not info["url"]:
if not info or not info['url'] or not info['sha256']:
continue
if not info["sha256"]:
print(f" {package}: WARNING - skipped fallback without sha256")
# Determine filename from URL
url = info['url']
expected_sha256 = info['sha256']
url_filename = os.path.basename(url)
# getdeps uses format: {package}-{filename}
filename = f"{package}-{url_filename}"
filepath = os.path.join(download_dir, filename)
cache_path = os.path.join(cache_dir, filename)
# Check if already valid
if os.path.exists(filepath) and sha256_file(filepath) == expected_sha256:
print(f" {filename}: OK (already downloaded)")
continue
if not get_fallback_mirrors(info["url"]):
print(
f" {package}: WARNING - skipped fallback without known mirror "
f"for {info['url']}"
)
# Check cache
if os.path.exists(cache_path) and sha256_file(cache_path) == expected_sha256:
print(f" {filename}: OK (from cache)")
subprocess.run(['cp', cache_path, filepath], check=True)
continue
checked += 1
try:
if prepare_download(package, info, download_dir, cache_dir):
ready += 1
except Exception as ex:
print(f" {package}: WARNING - fallback preparation failed: {ex}")
# Try fallback mirrors
mirrors = get_fallback_mirrors(url)
downloaded = False
for mirror_url in mirrors:
print(f" {filename}: trying {mirror_url}...")
try:
subprocess.run(['wget', '-q', '-O', filepath, mirror_url], check=True, timeout=120)
if sha256_file(filepath) == expected_sha256:
print(f" {filename}: OK (downloaded)")
subprocess.run(['cp', filepath, cache_path], check=False)
downloaded = True
break
else:
os.remove(filepath)
except Exception:
if os.path.exists(filepath):
os.remove(filepath)
print(f" fallback mirror downloads ready: {ready}/{checked}")
if not downloaded:
print(f" {filename}: WARNING - all mirrors failed")
if __name__ == "__main__":
main()
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env python3
# Copyright 2017 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import gtest_parallel
import sys
sys.exit(gtest_parallel.main())
-959
View File
@@ -1,959 +0,0 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
# Copyright 2013 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
from functools import total_ordering
import gzip
import io
import json
import multiprocessing
import optparse
import os
import re
import shutil
import signal
import subprocess
import sys
import tempfile
import threading
import time
if sys.version_info.major >= 3:
long = int
import _pickle as cPickle
import _thread as thread
else:
import cPickle
import thread
from pickle import HIGHEST_PROTOCOL as PICKLE_HIGHEST_PROTOCOL
if sys.platform == 'win32':
import msvcrt
else:
import fcntl
# An object that catches SIGINT sent to the Python process and notices
# if processes passed to wait() die by SIGINT (we need to look for
# both of those cases, because pressing Ctrl+C can result in either
# the main process or one of the subprocesses getting the signal).
#
# Before a SIGINT is seen, wait(p) will simply call p.wait() and
# return the result. Once a SIGINT has been seen (in the main process
# or a subprocess, including the one the current call is waiting for),
# wait(p) will call p.terminate() and raise ProcessWasInterrupted.
class SigintHandler(object):
class ProcessWasInterrupted(Exception):
pass
sigint_returncodes = {
-signal.SIGINT, # Unix
-1073741510, # Windows
}
def __init__(self):
self.__lock = threading.Lock()
self.__processes = set()
self.__got_sigint = False
signal.signal(signal.SIGINT, lambda signal_num, frame: self.interrupt())
def __on_sigint(self):
self.__got_sigint = True
while self.__processes:
try:
self.__processes.pop().terminate()
except OSError:
pass
def interrupt(self):
with self.__lock:
self.__on_sigint()
def got_sigint(self):
with self.__lock:
return self.__got_sigint
def wait(self, p, timeout_per_test):
with self.__lock:
if self.__got_sigint:
p.terminate()
self.__processes.add(p)
try:
code = p.wait(timeout_per_test)
except subprocess.TimeoutExpired :
p.terminate()
self.__processes.remove(p)
code = -errno.ETIME
with self.__lock:
self.__processes.discard(p)
if code in self.sigint_returncodes:
self.__on_sigint()
if self.__got_sigint:
raise self.ProcessWasInterrupted
return code
sigint_handler = SigintHandler()
# Return the width of the terminal, or None if it couldn't be
# determined (e.g. because we're not being run interactively).
def term_width(out):
if not out.isatty():
return None
try:
p = subprocess.Popen(["stty", "size"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(out, err) = p.communicate()
if p.returncode != 0 or err:
return None
return int(out.split()[1])
except (IndexError, OSError, ValueError):
return None
# Output transient and permanent lines of text. If several transient
# lines are written in sequence, the new will overwrite the old. We
# use this to ensure that lots of unimportant info (tests passing)
# won't drown out important info (tests failing).
class Outputter(object):
def __init__(self, out_file):
self.__out_file = out_file
self.__previous_line_was_transient = False
self.__width = term_width(out_file) # Line width, or None if not a tty.
def transient_line(self, msg):
if self.__width is None:
self.__out_file.write(msg + "\n")
self.__out_file.flush()
else:
self.__out_file.write("\r" + msg[:self.__width].ljust(self.__width))
self.__previous_line_was_transient = True
def flush_transient_output(self):
if self.__previous_line_was_transient:
self.__out_file.write("\n")
self.__previous_line_was_transient = False
def permanent_line(self, msg):
self.flush_transient_output()
self.__out_file.write(msg + "\n")
if self.__width is None:
self.__out_file.flush()
def get_save_file_path():
"""Return path to file for saving transient data."""
if sys.platform == 'win32':
default_cache_path = os.path.join(os.path.expanduser('~'), 'AppData',
'Local')
cache_path = os.environ.get('LOCALAPPDATA', default_cache_path)
else:
# We don't use xdg module since it's not a standard.
default_cache_path = os.path.join(os.path.expanduser('~'), '.cache')
cache_path = os.environ.get('XDG_CACHE_HOME', default_cache_path)
if os.path.isdir(cache_path):
return os.path.join(cache_path, 'gtest-parallel')
else:
sys.stderr.write('Directory {} does not exist'.format(cache_path))
return os.path.join(os.path.expanduser('~'), '.gtest-parallel-times')
@total_ordering
class Task(object):
"""Stores information about a task (single execution of a test).
This class stores information about the test to be executed (gtest binary and
test name), and its result (log file, exit code and runtime).
Each task is uniquely identified by the gtest binary, the test name and an
execution number that increases each time the test is executed.
Additionaly we store the last execution time, so that next time the test is
executed, the slowest tests are run first.
"""
def __init__(self, test_binary, test_name, test_command, execution_number,
last_execution_time, output_dir):
self.test_name = test_name
self.output_dir = output_dir
self.test_binary = test_binary
self.test_command = test_command
self.execution_number = execution_number
self.last_execution_time = last_execution_time
self.exit_code = None
self.runtime_ms = None
self.test_id = (test_binary, test_name)
self.task_id = (test_binary, test_name, self.execution_number)
self.log_file = Task._logname(self.output_dir, self.test_binary, test_name,
self.execution_number)
def __sorting_key(self):
# Unseen or failing tests (both missing execution time) take precedence over
# execution time. Tests are greater (seen as slower) when missing times so
# that they are executed first.
return (1 if self.last_execution_time is None else 0,
self.last_execution_time)
def __eq__(self, other):
return self.__sorting_key() == other.__sorting_key()
def __ne__(self, other):
return not (self == other)
def __lt__(self, other):
return self.__sorting_key() < other.__sorting_key()
@staticmethod
def _normalize(string):
return re.sub('[^A-Za-z0-9]', '_', string)
@staticmethod
def _logname(output_dir, test_binary, test_name, execution_number):
# Store logs to temporary files if there is no output_dir.
if output_dir is None:
(log_handle, log_name) = tempfile.mkstemp(prefix='gtest_parallel_',
suffix=".log")
os.close(log_handle)
return log_name
log_name = '%s-%s-%d.log' % (Task._normalize(os.path.basename(test_binary)),
Task._normalize(test_name), execution_number)
return os.path.join(output_dir, log_name)
def run(self, timeout_per_test):
begin = time.time()
with open(self.log_file, 'w') as log:
task = subprocess.Popen(self.test_command, stdout=log, stderr=log)
try:
self.exit_code = sigint_handler.wait(task, timeout_per_test)
except sigint_handler.ProcessWasInterrupted:
thread.exit()
self.runtime_ms = int(1000 * (time.time() - begin))
self.last_execution_time = None if self.exit_code else self.runtime_ms
class TaskManager(object):
"""Executes the tasks and stores the passed, failed and interrupted tasks.
When a task is run, this class keeps track if it passed, failed or was
interrupted. After a task finishes it calls the relevant functions of the
Logger, TestResults and TestTimes classes, and in case of failure, retries the
test as specified by the --retry_failed flag.
"""
def __init__(self, times, logger, test_results, task_factory, times_to_retry,
initial_execution_number):
self.times = times
self.logger = logger
self.test_results = test_results
self.task_factory = task_factory
self.times_to_retry = times_to_retry
self.initial_execution_number = initial_execution_number
self.global_exit_code = 0
self.passed = []
self.failed = []
self.started = {}
self.timed_out = []
self.execution_number = {}
self.lock = threading.Lock()
def __get_next_execution_number(self, test_id):
with self.lock:
next_execution_number = self.execution_number.setdefault(
test_id, self.initial_execution_number)
self.execution_number[test_id] += 1
return next_execution_number
def __register_start(self, task):
with self.lock:
self.started[task.task_id] = task
def register_exit(self, task):
self.logger.log_exit(task)
self.times.record_test_time(task.test_binary, task.test_name,
task.last_execution_time)
if self.test_results:
self.test_results.log(task.test_name, task.runtime_ms / 1000.0,
task.exit_code)
with self.lock:
self.started.pop(task.task_id)
if task.exit_code == 0:
self.passed.append(task)
elif task.exit_code == -errno.ETIME:
self.timed_out.append(task)
else:
self.failed.append(task)
def run_task(self, task, timeout_per_test):
for try_number in range(self.times_to_retry + 1):
self.__register_start(task)
task.run(timeout_per_test)
self.register_exit(task)
if task.exit_code == 0:
break
if try_number < self.times_to_retry:
execution_number = self.__get_next_execution_number(task.test_id)
# We need create a new Task instance. Each task represents a single test
# execution, with its own runtime, exit code and log file.
task = self.task_factory(task.test_binary, task.test_name,
task.test_command, execution_number,
task.last_execution_time, task.output_dir)
with self.lock:
if task.exit_code != 0:
self.global_exit_code = task.exit_code
class FilterFormat(object):
def __init__(self, output_dir):
if sys.stdout.isatty():
# stdout needs to be unbuffered since the output is interactive.
if isinstance(sys.stdout, io.TextIOWrapper):
# workaround for https://bugs.python.org/issue17404
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
line_buffering=True,
write_through=True,
newline='\n')
else:
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
self.output_dir = output_dir
self.total_tasks = 0
self.finished_tasks = 0
self.out = Outputter(sys.stdout)
self.stdout_lock = threading.Lock()
def move_to(self, destination_dir, tasks):
if self.output_dir is None:
return
destination_dir = os.path.join(self.output_dir, destination_dir)
os.makedirs(destination_dir)
for task in tasks:
shutil.move(task.log_file, destination_dir)
def print_tests(self, message, tasks, print_try_number, print_test_command):
self.out.permanent_line("%s (%s/%s):" %
(message, len(tasks), self.total_tasks))
for task in sorted(tasks):
runtime_ms = 'Interrupted'
if task.runtime_ms is not None:
runtime_ms = '%d ms' % task.runtime_ms
if print_test_command:
try:
cmd_str = " ".join(task.test_command)
except TypeError:
cmd_str = task.test_command
self.out.permanent_line(
"%11s: %s%s" %
(runtime_ms, cmd_str,
(" (try #%d)" % task.execution_number) if print_try_number else ""))
else:
self.out.permanent_line(
"%11s: %s %s%s" %
(runtime_ms, task.test_binary, task.test_name,
(" (try #%d)" % task.execution_number) if print_try_number else ""))
def log_exit(self, task):
with self.stdout_lock:
self.finished_tasks += 1
self.out.transient_line("[%d/%d] %s (%d ms)" %
(self.finished_tasks, self.total_tasks,
task.test_name, task.runtime_ms))
if task.exit_code != 0:
signal_name = None
if task.exit_code < 0:
try:
signal_name = signal.Signals(-task.exit_code).name
except ValueError:
pass
with open(task.log_file) as f:
for line in f.readlines():
self.out.permanent_line(line.rstrip())
if task.exit_code is None:
self.out.permanent_line("[%d/%d] %s aborted after %d ms" %
(self.finished_tasks, self.total_tasks,
task.test_name, task.runtime_ms))
elif task.exit_code == -errno.ETIME:
self.out.permanent_line(
"\033[31m[ TIMEOUT ]\033[0m %s timed out after %d s"
% (task.test_name, task.runtime_ms/1000))
elif signal_name is not None:
self.out.permanent_line(
"[%d/%d] %s killed by signal %s (%d ms)" %
(self.finished_tasks, self.total_tasks, task.test_name,
signal_name, task.runtime_ms))
else:
self.out.permanent_line(
"[%d/%d] %s returned with exit code %d (%d ms)" %
(self.finished_tasks, self.total_tasks, task.test_name,
task.exit_code, task.runtime_ms))
if self.output_dir is None:
# Try to remove the file 100 times (sleeping for 0.1 second in between).
# This is a workaround for a process handle seemingly holding on to the
# file for too long inside os.subprocess. This workaround is in place
# until we figure out a minimal repro to report upstream (or a better
# suspect) to prevent os.remove exceptions.
num_tries = 100
for i in range(num_tries):
try:
os.remove(task.log_file)
except OSError as e:
if e.errno is not errno.ENOENT:
if i is num_tries - 1:
self.out.permanent_line('Could not remove temporary log file: ' +
str(e))
else:
time.sleep(0.1)
continue
break
def log_tasks(self, total_tasks):
self.total_tasks += total_tasks
self.out.transient_line("[0/%d] Running tests..." % self.total_tasks)
def summarize(self, passed_tasks, failed_tasks, interrupted_tasks):
stats = {}
def add_stats(stats, task, idx):
task_key = (task.test_binary, task.test_name)
if not task_key in stats:
# (passed, failed, interrupted) task_key is added as tie breaker to get
# alphabetic sorting on equally-stable tests
stats[task_key] = [0, 0, 0, task_key]
stats[task_key][idx] += 1
for task in passed_tasks:
add_stats(stats, task, 0)
for task in failed_tasks:
add_stats(stats, task, 1)
for task in interrupted_tasks:
add_stats(stats, task, 2)
self.out.permanent_line("SUMMARY:")
for task_key in sorted(stats, key=stats.__getitem__):
(num_passed, num_failed, num_interrupted, _) = stats[task_key]
(test_binary, task_name) = task_key
total_runs = num_passed + num_failed + num_interrupted
if num_passed == total_runs:
continue
self.out.permanent_line(" %s %s passed %d / %d times%s." %
(test_binary, task_name, num_passed, total_runs,
"" if num_interrupted == 0 else
(" (%d interrupted)" % num_interrupted)))
def flush(self):
self.out.flush_transient_output()
class CollectTestResults(object):
def __init__(self, json_dump_filepath):
self.test_results_lock = threading.Lock()
self.json_dump_file = open(json_dump_filepath, 'w')
self.test_results = {
"interrupted": False,
"path_delimiter": ".",
# Third version of the file format. See the link in the flag description
# for details.
"version": 3,
"seconds_since_epoch": int(time.time()),
"num_failures_by_type": {
"PASS": 0,
"FAIL": 0,
"TIMEOUT": 0,
},
"tests": {},
}
def log(self, test, runtime_seconds, exit_code):
if exit_code is None:
actual_result = "TIMEOUT"
elif exit_code == 0:
actual_result = "PASS"
else:
actual_result = "FAIL"
with self.test_results_lock:
self.test_results['num_failures_by_type'][actual_result] += 1
results = self.test_results['tests']
for name in test.split('.'):
results = results.setdefault(name, {})
if results:
results['actual'] += ' ' + actual_result
results['times'].append(runtime_seconds)
else: # This is the first invocation of the test
results['actual'] = actual_result
results['times'] = [runtime_seconds]
results['time'] = runtime_seconds
results['expected'] = 'PASS'
def dump_to_file_and_close(self):
json.dump(self.test_results, self.json_dump_file)
self.json_dump_file.close()
# Record of test runtimes. Has built-in locking.
class TestTimes(object):
class LockedFile(object):
def __init__(self, filename, mode):
self._filename = filename
self._mode = mode
self._fo = None
def __enter__(self):
self._fo = open(self._filename, self._mode)
# Regardless of opening mode we always seek to the beginning of file.
# This simplifies code working with LockedFile and also ensures that
# we lock (and unlock below) always the same region in file on win32.
self._fo.seek(0)
try:
if sys.platform == 'win32':
# We are locking here fixed location in file to use it as
# an exclusive lock on entire file.
msvcrt.locking(self._fo.fileno(), msvcrt.LK_LOCK, 1)
else:
fcntl.flock(self._fo.fileno(), fcntl.LOCK_EX)
except IOError:
self._fo.close()
raise
return self._fo
def __exit__(self, exc_type, exc_value, traceback):
# Flush any buffered data to disk. This is needed to prevent race
# condition which happens from the moment of releasing file lock
# till closing the file.
self._fo.flush()
try:
if sys.platform == 'win32':
self._fo.seek(0)
msvcrt.locking(self._fo.fileno(), msvcrt.LK_UNLCK, 1)
else:
fcntl.flock(self._fo.fileno(), fcntl.LOCK_UN)
finally:
self._fo.close()
return exc_value is None
def __init__(self, save_file):
"Create new object seeded with saved test times from the given file."
self.__times = {} # (test binary, test name) -> runtime in ms
# Protects calls to record_test_time(); other calls are not
# expected to be made concurrently.
self.__lock = threading.Lock()
try:
with TestTimes.LockedFile(save_file, 'rb') as fd:
times = TestTimes.__read_test_times_file(fd)
except IOError:
# We couldn't obtain the lock.
return
# Discard saved times if the format isn't right.
if type(times) is not dict:
return
for ((test_binary, test_name), runtime) in times.items():
if (type(test_binary) is not str or type(test_name) is not str
or type(runtime) not in {int, long, type(None)}):
return
self.__times = times
def get_test_time(self, binary, testname):
"""Return the last duration for the given test as an integer number of
milliseconds, or None if the test failed or if there's no record for it."""
return self.__times.get((binary, testname), None)
def record_test_time(self, binary, testname, runtime_ms):
"""Record that the given test ran in the specified number of
milliseconds. If the test failed, runtime_ms should be None."""
with self.__lock:
self.__times[(binary, testname)] = runtime_ms
def write_to_file(self, save_file):
"Write all the times to file."
try:
with TestTimes.LockedFile(save_file, 'a+b') as fd:
times = TestTimes.__read_test_times_file(fd)
if times is None:
times = self.__times
else:
times.update(self.__times)
# We erase data from file while still holding a lock to it. This
# way reading old test times and appending new ones are atomic
# for external viewer.
fd.seek(0)
fd.truncate()
with gzip.GzipFile(fileobj=fd, mode='wb') as gzf:
cPickle.dump(times, gzf, PICKLE_HIGHEST_PROTOCOL)
except IOError:
pass # ignore errors---saving the times isn't that important
@staticmethod
def __read_test_times_file(fd):
try:
with gzip.GzipFile(fileobj=fd, mode='rb') as gzf:
times = cPickle.load(gzf)
except Exception:
# File doesn't exist, isn't readable, is malformed---whatever.
# Just ignore it.
return None
else:
return times
def find_tests(binaries, additional_args, options, times):
test_count = 0
tasks = []
for test_binary in binaries:
command = [test_binary] + additional_args
if options.gtest_also_run_disabled_tests:
command += ['--gtest_also_run_disabled_tests']
list_command = command + ['--gtest_list_tests']
if options.gtest_filter != '':
list_command += ['--gtest_filter=' + options.gtest_filter]
try:
test_list = subprocess.check_output(list_command,
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
sys.exit("%s: %s\n%s" % (test_binary, str(e), e.output))
try:
test_list = test_list.split('\n')
except TypeError:
# subprocess.check_output() returns bytes in python3
test_list = test_list.decode(sys.stdout.encoding).split('\n')
command += ['--gtest_color=' + options.gtest_color]
test_group = ''
for line in test_list:
if not line.strip():
continue
if line[0] != " ":
# Remove comments for typed tests and strip whitespace.
test_group = line.split('#')[0].strip()
continue
# Remove comments for parameterized tests and strip whitespace.
line = line.split('#')[0].strip()
if not line:
continue
test_name = test_group + line
if not options.gtest_also_run_disabled_tests and 'DISABLED_' in test_name:
continue
# Skip PRE_ tests which are used by Chromium.
if '.PRE_' in test_name:
continue
last_execution_time = times.get_test_time(test_binary, test_name)
if options.failed and last_execution_time is not None:
continue
test_command = command + ['--gtest_filter=' + test_name]
if (test_count - options.shard_index) % options.shard_count == 0:
for execution_number in range(options.repeat):
tasks.append(
Task(test_binary, test_name, test_command, execution_number + 1,
last_execution_time, options.output_dir))
test_count += 1
# Sort the tasks to run the slowest tests first, so that faster ones can be
# finished in parallel.
return sorted(tasks, reverse=True)
def execute_tasks(tasks, pool_size, task_manager, timeout_seconds,
timeout_per_test, serialize_test_cases):
class WorkerFn(object):
def __init__(self, tasks, running_groups, timeout_per_test):
self.tasks = tasks
self.running_groups = running_groups
self.timeout_per_test = timeout_per_test
self.task_lock = threading.Lock()
def __call__(self):
while True:
with self.task_lock:
for task_id in range(len(self.tasks)):
task = self.tasks[task_id]
if self.running_groups is not None:
test_group = task.test_name.split('.')[0]
if test_group in self.running_groups:
# Try to find other non-running test group.
continue
else:
self.running_groups.add(test_group)
del self.tasks[task_id]
break
else:
# Either there is no tasks left or number or remaining test
# cases (groups) is less than number or running threads.
return
task_manager.run_task(task, self.timeout_per_test)
if self.running_groups is not None:
with self.task_lock:
self.running_groups.remove(test_group)
def start_daemon(func):
t = threading.Thread(target=func)
t.daemon = True
t.start()
return t
timeout = None
try:
if timeout_seconds:
timeout = threading.Timer(timeout_seconds, sigint_handler.interrupt)
timeout.start()
running_groups = set() if serialize_test_cases else None
worker_fn = WorkerFn(tasks, running_groups, timeout_per_test)
workers = [start_daemon(worker_fn) for _ in range(pool_size)]
for worker in workers:
worker.join()
finally:
if timeout:
timeout.cancel()
for task in list(task_manager.started.values()):
task.runtime_ms = timeout_seconds * 1000
task_manager.register_exit(task)
def default_options_parser():
parser = optparse.OptionParser(
usage='usage: %prog [options] binary [binary ...] -- [additional args]')
parser.add_option('-d',
'--output_dir',
type='string',
default=None,
help='Output directory for test logs. Logs will be '
'available under gtest-parallel-logs/, so '
'--output_dir=/tmp will results in all logs being '
'available under /tmp/gtest-parallel-logs/.')
parser.add_option('-r',
'--repeat',
type='int',
default=1,
help='Number of times to execute all the tests.')
parser.add_option('--retry_failed',
type='int',
default=0,
help='Number of times to repeat failed tests.')
parser.add_option('--failed',
action='store_true',
default=False,
help='run only failed and new tests')
parser.add_option('-w',
'--workers',
type='int',
default=multiprocessing.cpu_count(),
help='number of workers to spawn')
parser.add_option('--gtest_color',
type='string',
default='yes',
help='color output')
parser.add_option('--gtest_filter',
type='string',
default='',
help='test filter')
parser.add_option('--gtest_also_run_disabled_tests',
action='store_true',
default=False,
help='run disabled tests too')
parser.add_option(
'--print_test_times',
action='store_true',
default=False,
help='list the run time of each test at the end of execution')
parser.add_option(
'--print_test_command',
action='store_true',
default=False,
help='Print full test command instead of name')
parser.add_option('--shard_count',
type='int',
default=1,
help='total number of shards (for sharding test execution '
'between multiple machines)')
parser.add_option('--shard_index',
type='int',
default=0,
help='zero-indexed number identifying this shard (for '
'sharding test execution between multiple machines)')
parser.add_option(
'--dump_json_test_results',
type='string',
default=None,
help='Saves the results of the tests as a JSON machine-'
'readable file. The format of the file is specified at '
'https://www.chromium.org/developers/the-json-test-results-format')
parser.add_option('--timeout',
type='int',
default=None,
help='Interrupt all remaining processes after the given '
'time (in seconds).')
parser.add_option('--timeout_per_test',
type='int',
default=None,
help='Interrupt single processes after the given '
'time (in seconds).')
parser.add_option('--serialize_test_cases',
action='store_true',
default=False,
help='Do not run tests from the same test '
'case in parallel.')
return parser
def main():
# Remove additional arguments (anything after --).
additional_args = []
for i in range(len(sys.argv)):
if sys.argv[i] == '--':
additional_args = sys.argv[i + 1:]
sys.argv = sys.argv[:i]
break
parser = default_options_parser()
(options, binaries) = parser.parse_args()
if (options.output_dir is not None and not os.path.isdir(options.output_dir)):
parser.error('--output_dir value must be an existing directory, '
'current value is "%s"' % options.output_dir)
# Append gtest-parallel-logs to log output, this is to avoid deleting user
# data if an user passes a directory where files are already present. If a
# user specifies --output_dir=Docs/, we'll create Docs/gtest-parallel-logs
# and clean that directory out on startup, instead of nuking Docs/.
if options.output_dir:
options.output_dir = os.path.join(options.output_dir, 'gtest-parallel-logs')
if binaries == []:
parser.print_usage()
sys.exit(1)
if options.shard_count < 1:
parser.error("Invalid number of shards: %d. Must be at least 1." %
options.shard_count)
if not (0 <= options.shard_index < options.shard_count):
parser.error("Invalid shard index: %d. Must be between 0 and %d "
"(less than the number of shards)." %
(options.shard_index, options.shard_count - 1))
# Check that all test binaries have an unique basename. That way we can ensure
# the logs are saved to unique files even when two different binaries have
# common tests.
unique_binaries = set(os.path.basename(binary) for binary in binaries)
assert len(unique_binaries) == len(binaries), (
"All test binaries must have an unique basename.")
if options.output_dir:
# Remove files from old test runs.
if os.path.isdir(options.output_dir):
shutil.rmtree(options.output_dir)
# Create directory for test log output.
try:
os.makedirs(options.output_dir)
except OSError as e:
# Ignore errors if this directory already exists.
if e.errno != errno.EEXIST or not os.path.isdir(options.output_dir):
raise e
test_results = None
if options.dump_json_test_results is not None:
test_results = CollectTestResults(options.dump_json_test_results)
save_file = get_save_file_path()
times = TestTimes(save_file)
logger = FilterFormat(options.output_dir)
task_manager = TaskManager(times, logger, test_results, Task,
options.retry_failed, options.repeat + 1)
tasks = find_tests(binaries, additional_args, options, times)
logger.log_tasks(len(tasks))
execute_tasks(tasks, options.workers, task_manager, options.timeout,
options.timeout_per_test, options.serialize_test_cases)
print_try_number = options.retry_failed > 0 or options.repeat > 1
if task_manager.passed:
logger.move_to('passed', task_manager.passed)
if options.print_test_times:
logger.print_tests('PASSED TESTS', task_manager.passed, print_try_number, options.print_test_command)
if task_manager.failed:
logger.print_tests('FAILED TESTS', task_manager.failed, print_try_number, options.print_test_command)
logger.move_to('failed', task_manager.failed)
if task_manager.timed_out:
logger.print_tests('TIMED OUT TESTS', task_manager.timed_out, print_try_number, options.print_test_command)
logger.move_to('timed_out', task_manager.timed_out)
if task_manager.started:
logger.print_tests('INTERRUPTED TESTS', task_manager.started.values(),
print_try_number, options.print_test_command)
logger.move_to('interrupted', task_manager.started.values())
if options.repeat > 1 and (task_manager.failed or task_manager.started):
logger.summarize(task_manager.passed, task_manager.failed,
task_manager.started.values())
logger.flush()
times.write_to_file(save_file)
if test_results:
test_results.dump_to_file_and_close()
if sigint_handler.got_sigint():
return -signal.SIGINT
return task_manager.global_exit_code
if __name__ == "__main__":
sys.exit(main())
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the
# COPYING file in the root directory) and Apache 2.0 License
# (found in the LICENSE.Apache file in the root directory).
#
# Build RocksDB unit test binaries and run them under gtest-parallel,
# which shards the test cases across CPUs for faster execution.
#
# Hardened version of a simple `make <bin> && gtest-parallel ./<bin>` helper:
# * AUTO_CLEAN=1 so the Makefile automatically runs a clean when the build
# parameters (DEBUG_LEVEL, sanitizers, ASSERT_STATUS_CHECKED, RTTI, etc.)
# have changed since the last build, preventing stale/mixed object files.
# * Parallel build with -j<NCORES>, computed the same way the Makefile does.
# * Uses the gtest-parallel checked in alongside this script (build_tools/),
# so it works regardless of PATH.
# * `set -euo pipefail` so any failure stops the script.
#
# Run from the repository root.
#
# Accepts one or more test binaries (gtest-parallel pools all their test cases
# into one shared worker queue). The leading non-option arguments are treated as
# binaries; everything from the first option onward is forwarded to
# gtest-parallel / the test binaries.
#
# Example usage:
# build_tools/rocksptest.sh db_test
# build_tools/rocksptest.sh db_test -r1000 --gtest_filter=*MixedSlowdown*
# build_tools/rocksptest.sh db_test env_test db_basic_test
# build_tools/rocksptest.sh db_test env_test --gtest_filter=*Foo*
set -euo pipefail
if [ "$#" -lt 1 ]; then
echo "usage: $0 <test_binary> [more_test_binaries...] [gtest-parallel/test args...]" >&2
echo "example: $0 db_test env_test -r1000 --gtest_filter=*MixedSlowdown*" >&2
exit 1
fi
# First argument must be a test binary, not an option (catches a forgotten name).
if [ "${1#-}" != "$1" ]; then
echo "$0: first argument must be a test binary name, not an option ('$1')" >&2
exit 1
fi
# Collect the leading non-option arguments as test binaries; everything from the
# first option onward is forwarded to gtest-parallel / the test binaries.
BINS=()
while [ "$#" -gt 0 ] && [ "${1#-}" = "$1" ]; do
BINS+=("$1")
shift
done
# Paths as gtest-parallel expects them (relative to the current directory).
BIN_PATHS=()
for b in "${BINS[@]}"; do
BIN_PATHS+=("./$b")
done
# Directory of this script, so we use the gtest-parallel checked in next to it.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# Compute parallelism the same way the Makefile computes NCORES.
NCORES=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
make AUTO_CLEAN=1 -j"$NCORES" "${BINS[@]}"
"$SCRIPT_DIR/gtest-parallel" "${BIN_PATHS[@]}" "$@"
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the
# COPYING file in the root directory) and Apache 2.0 License
# (found in the LICENSE.Apache file in the root directory).
#
# Build a single RocksDB unit test binary and run it directly (serially).
# Only recommended with --gtest_filter=... because of speed. See also
# build_tools/rocksptest.sh
#
# Hardened version of a simple `make <bin> && ./<bin>` helper:
# * AUTO_CLEAN=1 so the Makefile automatically runs a clean when the build
# parameters (DEBUG_LEVEL, sanitizers, ASSERT_STATUS_CHECKED, RTTI, etc.)
# have changed since the last build, preventing stale/mixed object files.
# * Parallel build with -j<NCORES>, computed the same way the Makefile does.
# * `set -euo pipefail` so any failure stops the script.
#
# Run from the repository root.
#
# Example usage:
# build_tools/rockstest.sh db_test --gtest_filter=*MixedSlowdown*
# build_tools/rockstest.sh env_test # Slow to run many tests serially
#
# Install mode:
# build_tools/rockstest.sh install
# Creates ~/bin/rockstest and ~/bin/rocksptest shims that defer to
# build_tools/rockstest.sh / rocksptest.sh in whatever directory you run
# them from, so you can just type `rockstest db_test` from any rocksdb
# source root.
set -euo pipefail
# Install mode: write thin ~/bin shims that defer to the build_tools scripts in
# the current directory.
if [ "${1:-}" = "install" ]; then
mkdir -p "$HOME/bin"
for name in rockstest rocksptest; do
dest="$HOME/bin/$name"
cat > "$dest" <<EOF
#!/usr/bin/env bash
# Auto-generated by 'build_tools/rockstest.sh install'. Defers to
# build_tools/$name.sh in the current rocksdb source root.
if [ -x build_tools/$name.sh ]; then
exec build_tools/$name.sh "\$@"
else
echo "build_tools/$name.sh not found (Not in a rocksdb source root directory?)" >&2
exit 1
fi
EOF
chmod +x "$dest"
echo "Installed $dest"
done
exit 0
fi
if [ "$#" -lt 1 ]; then
echo "usage: $0 <test_binary> [test args...]" >&2
echo " $0 install # create ~/bin/rockstest and ~/bin/rocksptest shims" >&2
echo "example: $0 db_test --gtest_filter=*MixedSlowdown*" >&2
exit 1
fi
# First argument must be a test binary, not an option (catches a forgotten name).
if [ "${1#-}" != "$1" ]; then
echo "$0: first argument must be a test binary name, not an option ('$1')" >&2
exit 1
fi
BIN=$1
shift
# Compute parallelism the same way the Makefile computes NCORES.
NCORES=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
make AUTO_CLEAN=1 -j"$NCORES" "$BIN"
./"$BIN" "$@"
+9 -24
View File
@@ -32,7 +32,7 @@ function get_lib_base()
local lib_platform=$3
local result="$TP2_LATEST/$lib_name/"
# Lib Version
if [ -z "$lib_version" ] || [ "$lib_version" = "LATEST" ]; then
# version is not provided, use latest
@@ -40,7 +40,7 @@ function get_lib_base()
else
result="$result/$lib_version/"
fi
# Lib Platform
if [ -z "$lib_platform" ]; then
# platform is not provided, use latest gcc
@@ -49,17 +49,17 @@ function get_lib_base()
echo $lib_platform
result="$result/$lib_platform/"
fi
result=`ls -1d $result/*/ | head -n1`
echo Finding link $result
# lib_name => LIB_NAME_BASE
local __res_var=${lib_name^^}"_BASE"
__res_var=`echo $__res_var | tr - _`
# LIB_NAME_BASE=$result
eval $__res_var=`readlink -f $result`
log_variable $__res_var
}
@@ -75,33 +75,17 @@ touch "$OUTPUT"
echo "Writing dependencies to $OUTPUT"
# Compilers locations
# GCC is pinned to 11.x because the only newer GCC in third-party2 (13.x) is
# built for centos9/glibc>=2.35 -- its libgcc_s.so.1 has a hard reference
# to _dl_find_object@GLIBC_2.35, but platform010 ships glibc 2.34. Bumping
# GCC requires a platform with glibc >= 2.35.
GCC_BASE=`readlink -f $TP2_LATEST/gcc/11.x/centos9-native/*/`
# Clang is pinned to the latest tested major (21). Bump deliberately.
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/21/platform010/*/`
GCC_BASE=`readlink -f $TP2_LATEST/gcc/11.x/centos8-native/*/`
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/15/platform010/*/`
log_header
log_variable GCC_BASE
log_variable CLANG_BASE
# Libraries locations
# libgcc is pinned to 11.x to match the GCC 11 compiler above (libstdc++/
# libgcc runtime, ABI, and C++ headers). Bump in lockstep with GCC_BASE.
get_lib_base libgcc 11.x platform010
# glibc 2.34 is the platform010 ABI baseline (ld.so + libc); it is also the
# only version available in third-party2, and defines the platform -- do
# not bump independently.
get_lib_base glibc 2.34 platform010
get_lib_base snappy LATEST platform010
# zlib is pinned to 1.2.8: it is the latest version with an x86_64
# platform010 build (1.2.13 / 1.3.1 are centos9 / aarch64 only). At time of
# writing, LATEST here doesn't work: get_lib_base picks the newest version
# dir first and only then appends the platform, with no fallback -- so
# LATEST would resolve to 1.3.1, find no platform010 build, and emit an
# empty ZLIB_BASE.
get_lib_base zlib 1.2.8 platform010
get_lib_base bzip2 LATEST platform010
get_lib_base lz4 LATEST platform010
@@ -110,11 +94,12 @@ get_lib_base gflags LATEST platform010
get_lib_base jemalloc LATEST platform010
get_lib_base numa LATEST platform010
get_lib_base libunwind LATEST platform010
get_lib_base tbb 2018_U5 platform010
get_lib_base liburing LATEST platform010
get_lib_base benchmark LATEST platform010
get_lib_base kernel-headers fb platform010
get_lib_base binutils LATEST centos9-native
get_lib_base binutils LATEST centos8-native
get_lib_base valgrind LATEST platform010
git diff $OUTPUT
@@ -1,43 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'BlockBasedOptions simple'
// --header-out
// c_api_gen/c_generated_block_based_options_subset.h.inc
// --source-out
// c_api_gen/c_generated_block_based_options_subset.cc.inc
/* BlockBasedOptions simple */
void rocksdb_block_based_options_set_data_block_hash_ratio(
rocksdb_block_based_table_options_t* options, double v) {
options->rep.data_block_hash_table_util_ratio = v;
}
void rocksdb_block_based_options_set_top_level_index_pinning_tier(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.metadata_cache_options.top_level_index_pinning =
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
void rocksdb_block_based_options_set_partition_pinning_tier(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.metadata_cache_options.partition_pinning =
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
void rocksdb_block_based_options_set_unpartitioned_pinning_tier(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.metadata_cache_options.unpartitioned_pinning =
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
@@ -1,36 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'BlockBasedOptions simple'
// --header-out
// c_api_gen/c_generated_block_based_options_subset.h.inc
// --source-out
// c_api_gen/c_generated_block_based_options_subset.cc.inc
/* BlockBasedOptions simple */
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_data_block_hash_ratio(
rocksdb_block_based_table_options_t* options, double v);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_top_level_index_pinning_tier(
rocksdb_block_based_table_options_t* options, int v);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_partition_pinning_tier(
rocksdb_block_based_table_options_t* options, int v);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_unpartitioned_pinning_tier(
rocksdb_block_based_table_options_t* options, int v);
@@ -1,25 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'CuckooOptions simple'
// --header-out
// c_api_gen/c_generated_cuckoo_options_subset.h.inc
// --source-out
// c_api_gen/c_generated_cuckoo_options_subset.cc.inc
/* CuckooOptions simple */
void rocksdb_cuckoo_options_set_hash_ratio(
rocksdb_cuckoo_table_options_t* options, double v) {
options->rep.hash_table_ratio = v;
}
@@ -1,23 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'CuckooOptions simple'
// --header-out
// c_api_gen/c_generated_cuckoo_options_subset.h.inc
// --source-out
// c_api_gen/c_generated_cuckoo_options_subset.cc.inc
/* CuckooOptions simple */
extern ROCKSDB_LIBRARY_API void rocksdb_cuckoo_options_set_hash_ratio(
rocksdb_cuckoo_table_options_t* options, double v);
@@ -1,193 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'DB data operations'
// --header-out
// c_api_gen/c_generated_db_simple_subset.h.inc
// --source-out
// c_api_gen/c_generated_db_simple_subset.cc.inc
/* DB data operations */
void rocksdb_put(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* val, size_t vallen,
char** errptr) {
SaveError(errptr,
db->rep->Put(options->rep, Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_put_cf(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* val,
size_t vallen, char** errptr) {
SaveError(errptr, db->rep->Put(options->rep, column_family->rep,
Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_delete(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, Slice(key, keylen)));
}
void rocksdb_delete_cf(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, column_family->rep,
Slice(key, keylen)));
}
void rocksdb_merge(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* val,
size_t vallen, char** errptr) {
SaveError(errptr, db->rep->Merge(options->rep, Slice(key, keylen),
Slice(val, vallen)));
}
void rocksdb_merge_cf(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* val,
size_t vallen, char** errptr) {
SaveError(errptr, db->rep->Merge(options->rep, column_family->rep,
Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_write(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr) {
SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
}
void rocksdb_put_with_ts(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* ts,
size_t tslen, const char* val, size_t vallen,
char** errptr) {
SaveError(errptr, db->rep->Put(options->rep, Slice(key, keylen),
Slice(ts, tslen), Slice(val, vallen)));
}
void rocksdb_put_cf_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* ts,
size_t tslen, const char* val, size_t vallen,
char** errptr) {
SaveError(errptr,
db->rep->Put(options->rep, column_family->rep, Slice(key, keylen),
Slice(ts, tslen), Slice(val, vallen)));
}
void rocksdb_delete_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* ts,
size_t tslen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, Slice(key, keylen),
Slice(ts, tslen)));
}
void rocksdb_delete_cf_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* ts,
size_t tslen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, column_family->rep,
Slice(key, keylen), Slice(ts, tslen)));
}
void rocksdb_singledelete(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->SingleDelete(options->rep, Slice(key, keylen)));
}
void rocksdb_singledelete_cf(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->SingleDelete(options->rep, column_family->rep,
Slice(key, keylen)));
}
void rocksdb_singledelete_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
const char* key, size_t keylen,
const char* ts, size_t tslen, char** errptr) {
SaveError(errptr, db->rep->SingleDelete(options->rep, Slice(key, keylen),
Slice(ts, tslen)));
}
void rocksdb_singledelete_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr) {
SaveError(errptr,
db->rep->SingleDelete(options->rep, column_family->rep,
Slice(key, keylen), Slice(ts, tslen)));
}
void rocksdb_delete_range_cf(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* start_key, size_t start_key_len,
const char* end_key, size_t end_key_len,
char** errptr) {
SaveError(errptr, db->rep->DeleteRange(options->rep, column_family->rep,
Slice(start_key, start_key_len),
Slice(end_key, end_key_len)));
}
void rocksdb_flush(rocksdb_t* db, const rocksdb_flushoptions_t* options,
char** errptr) {
SaveError(errptr, db->rep->Flush(options->rep));
}
void rocksdb_flush_cf(rocksdb_t* db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family,
char** errptr) {
SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
}
void rocksdb_flush_wal(rocksdb_t* db, unsigned char sync, char** errptr) {
SaveError(errptr, db->rep->FlushWAL(sync));
}
void rocksdb_pause_background_work(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->PauseBackgroundWork());
}
void rocksdb_continue_background_work(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->ContinueBackgroundWork());
}
void rocksdb_disable_file_deletions(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->DisableFileDeletions());
}
void rocksdb_enable_file_deletions(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->EnableFileDeletions());
}
void rocksdb_verify_checksum(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->VerifyChecksum());
}
void rocksdb_verify_file_checksums(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->VerifyFileChecksums(ReadOptions()));
}
void rocksdb_destroy_db(const rocksdb_options_t* options, const char* name,
char** errptr) {
SaveError(errptr, DestroyDB(name, options->rep));
}
void rocksdb_repair_db(const rocksdb_options_t* options, const char* name,
char** errptr) {
SaveError(errptr, RepairDB(name, options->rep));
}
@@ -1,130 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'DB data operations'
// --header-out
// c_api_gen/c_generated_db_simple_subset.h.inc
// --source-out
// c_api_gen/c_generated_db_simple_subset.cc.inc
/* DB data operations */
extern ROCKSDB_LIBRARY_API void rocksdb_put(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_put_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_merge(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_merge_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_write(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_put_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* ts, size_t tslen, const char* val, size_t vallen,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_put_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, const char* val, size_t vallen,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_range_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* start_key,
size_t start_key_len, const char* end_key, size_t end_key_len,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_flush(
rocksdb_t* db, const rocksdb_flushoptions_t* options, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_flush_cf(
rocksdb_t* db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_flush_wal(rocksdb_t* db,
unsigned char sync,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_pause_background_work(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_continue_background_work(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_disable_file_deletions(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_enable_file_deletions(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_verify_checksum(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_verify_file_checksums(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_destroy_db(
const rocksdb_options_t* options, const char* name, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_repair_db(
const rocksdb_options_t* options, const char* name, char** errptr);
-259
View File
@@ -1,259 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/auto_simple_bindings.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Sources:
// - include/rocksdb/listener.h
// - tools/c_api_gen/auto_simple_bindings_blocklist.json
// - include/rocksdb/c.h
// - tools/c_api_gen/c_base.cc
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/auto_simple_bindings.py
// Output group: Auto-discovered JobInfo metadata simple
/* FlushJobInfo */
uint32_t rocksdb_flushjobinfo_cf_id(const rocksdb_flushjobinfo_t* info) {
return info->rep.cf_id;
}
const char* rocksdb_flushjobinfo_cf_name(const rocksdb_flushjobinfo_t* info,
size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
const char* rocksdb_flushjobinfo_file_path(const rocksdb_flushjobinfo_t* info,
size_t* size) {
*size = info->rep.file_path.size();
return info->rep.file_path.data();
}
uint64_t rocksdb_flushjobinfo_file_number(const rocksdb_flushjobinfo_t* info) {
return info->rep.file_number;
}
uint64_t rocksdb_flushjobinfo_oldest_blob_file_number(
const rocksdb_flushjobinfo_t* info) {
return info->rep.oldest_blob_file_number;
}
uint64_t rocksdb_flushjobinfo_thread_id(const rocksdb_flushjobinfo_t* info) {
return info->rep.thread_id;
}
int rocksdb_flushjobinfo_job_id(const rocksdb_flushjobinfo_t* info) {
return info->rep.job_id;
}
unsigned char rocksdb_flushjobinfo_triggered_writes_slowdown(
const rocksdb_flushjobinfo_t* info) {
return info->rep.triggered_writes_slowdown;
}
unsigned char rocksdb_flushjobinfo_triggered_writes_stop(
const rocksdb_flushjobinfo_t* info) {
return info->rep.triggered_writes_stop;
}
uint64_t rocksdb_flushjobinfo_smallest_seqno(
const rocksdb_flushjobinfo_t* info) {
return info->rep.smallest_seqno;
}
uint64_t rocksdb_flushjobinfo_largest_seqno(
const rocksdb_flushjobinfo_t* info) {
return info->rep.largest_seqno;
}
uint32_t rocksdb_flushjobinfo_flush_reason(const rocksdb_flushjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.flush_reason);
}
uint32_t rocksdb_flushjobinfo_blob_compression_type(
const rocksdb_flushjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.blob_compression_type);
}
/* CompactionJobInfo */
uint32_t rocksdb_compactionjobinfo_cf_id(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.cf_id;
}
const char* rocksdb_compactionjobinfo_cf_name(
const rocksdb_compactionjobinfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
void rocksdb_compactionjobinfo_status(const rocksdb_compactionjobinfo_t* info,
char** errptr) {
SaveError(errptr, info->rep.status);
}
uint64_t rocksdb_compactionjobinfo_thread_id(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.thread_id;
}
int rocksdb_compactionjobinfo_job_id(const rocksdb_compactionjobinfo_t* info) {
return info->rep.job_id;
}
int rocksdb_compactionjobinfo_num_l0_files(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.num_l0_files;
}
int rocksdb_compactionjobinfo_base_input_level(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.base_input_level;
}
int rocksdb_compactionjobinfo_output_level(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.output_level;
}
uint32_t rocksdb_compactionjobinfo_compaction_reason(
const rocksdb_compactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.compaction_reason);
}
uint32_t rocksdb_compactionjobinfo_compression(
const rocksdb_compactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.compression);
}
uint32_t rocksdb_compactionjobinfo_blob_compression_type(
const rocksdb_compactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.blob_compression_type);
}
unsigned char rocksdb_compactionjobinfo_aborted(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.aborted;
}
/* SubcompactionJobInfo */
uint32_t rocksdb_subcompactionjobinfo_cf_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.cf_id;
}
const char* rocksdb_subcompactionjobinfo_cf_name(
const rocksdb_subcompactionjobinfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
void rocksdb_subcompactionjobinfo_status(
const rocksdb_subcompactionjobinfo_t* info, char** errptr) {
SaveError(errptr, info->rep.status);
}
uint64_t rocksdb_subcompactionjobinfo_thread_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.thread_id;
}
int rocksdb_subcompactionjobinfo_job_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.job_id;
}
int rocksdb_subcompactionjobinfo_subcompaction_job_id(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.subcompaction_job_id;
}
int rocksdb_subcompactionjobinfo_base_input_level(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.base_input_level;
}
int rocksdb_subcompactionjobinfo_output_level(
const rocksdb_subcompactionjobinfo_t* info) {
return info->rep.output_level;
}
uint32_t rocksdb_subcompactionjobinfo_compaction_reason(
const rocksdb_subcompactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.compaction_reason);
}
uint32_t rocksdb_subcompactionjobinfo_compression(
const rocksdb_subcompactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.compression);
}
uint32_t rocksdb_subcompactionjobinfo_blob_compression_type(
const rocksdb_subcompactionjobinfo_t* info) {
return static_cast<uint32_t>(info->rep.blob_compression_type);
}
/* ExternalFileIngestionInfo */
const char* rocksdb_externalfileingestioninfo_cf_name(
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
const char* rocksdb_externalfileingestioninfo_external_file_path(
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
*size = info->rep.external_file_path.size();
return info->rep.external_file_path.data();
}
const char* rocksdb_externalfileingestioninfo_internal_file_path(
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
*size = info->rep.internal_file_path.size();
return info->rep.internal_file_path.data();
}
uint64_t rocksdb_externalfileingestioninfo_global_seqno(
const rocksdb_externalfileingestioninfo_t* info) {
return info->rep.global_seqno;
}
/* MemTableInfo */
const char* rocksdb_memtableinfo_cf_name(const rocksdb_memtableinfo_t* info,
size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
uint64_t rocksdb_memtableinfo_first_seqno(const rocksdb_memtableinfo_t* info) {
return info->rep.first_seqno;
}
uint64_t rocksdb_memtableinfo_earliest_seqno(
const rocksdb_memtableinfo_t* info) {
return info->rep.earliest_seqno;
}
uint64_t rocksdb_memtableinfo_num_entries(const rocksdb_memtableinfo_t* info) {
return info->rep.num_entries;
}
uint64_t rocksdb_memtableinfo_num_deletes(const rocksdb_memtableinfo_t* info) {
return info->rep.num_deletes;
}
const char* rocksdb_memtableinfo_newest_udt(const rocksdb_memtableinfo_t* info,
size_t* size) {
*size = info->rep.newest_udt.size();
return info->rep.newest_udt.data();
}
-177
View File
@@ -1,177 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/auto_simple_bindings.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Sources:
// - include/rocksdb/listener.h
// - tools/c_api_gen/auto_simple_bindings_blocklist.json
// - include/rocksdb/c.h
// - tools/c_api_gen/c_base.cc
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/auto_simple_bindings.py
// Output group: Auto-discovered JobInfo metadata simple
/* FlushJobInfo */
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_flushjobinfo_cf_id(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API const char* rocksdb_flushjobinfo_cf_name(
const rocksdb_flushjobinfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API const char* rocksdb_flushjobinfo_file_path(
const rocksdb_flushjobinfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_flushjobinfo_file_number(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_flushjobinfo_oldest_blob_file_number(
const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_flushjobinfo_thread_id(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_flushjobinfo_job_id(
const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_flushjobinfo_triggered_writes_slowdown(
const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_flushjobinfo_triggered_writes_stop(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_flushjobinfo_smallest_seqno(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_flushjobinfo_largest_seqno(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_flushjobinfo_flush_reason(const rocksdb_flushjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_flushjobinfo_blob_compression_type(const rocksdb_flushjobinfo_t* info);
/* CompactionJobInfo */
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_compactionjobinfo_cf_id(const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API const char* rocksdb_compactionjobinfo_cf_name(
const rocksdb_compactionjobinfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API void rocksdb_compactionjobinfo_status(
const rocksdb_compactionjobinfo_t* info, char** errptr);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compactionjobinfo_thread_id(const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_compactionjobinfo_job_id(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_compactionjobinfo_num_l0_files(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_compactionjobinfo_base_input_level(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_compactionjobinfo_output_level(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t rocksdb_compactionjobinfo_compaction_reason(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_compactionjobinfo_compression(const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_compactionjobinfo_blob_compression_type(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_compactionjobinfo_aborted(
const rocksdb_compactionjobinfo_t* info);
/* SubcompactionJobInfo */
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_subcompactionjobinfo_cf_id(const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API const char* rocksdb_subcompactionjobinfo_cf_name(
const rocksdb_subcompactionjobinfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API void rocksdb_subcompactionjobinfo_status(
const rocksdb_subcompactionjobinfo_t* info, char** errptr);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_subcompactionjobinfo_thread_id(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_subcompactionjobinfo_job_id(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int
rocksdb_subcompactionjobinfo_subcompaction_job_id(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_subcompactionjobinfo_base_input_level(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API int rocksdb_subcompactionjobinfo_output_level(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_subcompactionjobinfo_compaction_reason(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t rocksdb_subcompactionjobinfo_compression(
const rocksdb_subcompactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_subcompactionjobinfo_blob_compression_type(
const rocksdb_subcompactionjobinfo_t* info);
/* ExternalFileIngestionInfo */
extern ROCKSDB_LIBRARY_API const char*
rocksdb_externalfileingestioninfo_cf_name(
const rocksdb_externalfileingestioninfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_externalfileingestioninfo_external_file_path(
const rocksdb_externalfileingestioninfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_externalfileingestioninfo_internal_file_path(
const rocksdb_externalfileingestioninfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_externalfileingestioninfo_global_seqno(
const rocksdb_externalfileingestioninfo_t* info);
/* MemTableInfo */
extern ROCKSDB_LIBRARY_API const char* rocksdb_memtableinfo_cf_name(
const rocksdb_memtableinfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_memtableinfo_first_seqno(const rocksdb_memtableinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_memtableinfo_earliest_seqno(const rocksdb_memtableinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_memtableinfo_num_entries(const rocksdb_memtableinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_memtableinfo_num_deletes(const rocksdb_memtableinfo_t* info);
extern ROCKSDB_LIBRARY_API const char* rocksdb_memtableinfo_newest_udt(
const rocksdb_memtableinfo_t* info, size_t* size);
@@ -1,88 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'JobInfo metadata simple'
// --header-out
// c_api_gen/c_generated_jobinfo_metadata_subset.h.inc
// --source-out
// c_api_gen/c_generated_jobinfo_metadata_subset.cc.inc
/* JobInfo metadata simple */
size_t rocksdb_compactionjobinfo_input_files_count(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.input_files.size();
}
size_t rocksdb_compactionjobinfo_output_files_count(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.output_files.size();
}
uint64_t rocksdb_compactionjobinfo_elapsed_micros(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.elapsed_micros;
}
uint64_t rocksdb_compactionjobinfo_num_corrupt_keys(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_corrupt_keys;
}
uint64_t rocksdb_compactionjobinfo_input_records(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_input_records;
}
uint64_t rocksdb_compactionjobinfo_output_records(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_output_records;
}
uint64_t rocksdb_compactionjobinfo_total_input_bytes(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.total_input_bytes;
}
uint64_t rocksdb_compactionjobinfo_total_output_bytes(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.total_output_bytes;
}
size_t rocksdb_compactionjobinfo_num_input_files(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_input_files;
}
size_t rocksdb_compactionjobinfo_num_input_files_at_output_level(
const rocksdb_compactionjobinfo_t* info) {
return info->rep.stats.num_input_files_at_output_level;
}
const char* rocksdb_writestallinfo_cf_name(const rocksdb_writestallinfo_t* info,
size_t* size) {
*size = info->rep.cf_name.size();
return info->rep.cf_name.data();
}
const rocksdb_writestallcondition_t* rocksdb_writestallinfo_cur(
const rocksdb_writestallinfo_t* info) {
return reinterpret_cast<const rocksdb_writestallcondition_t*>(
&info->rep.condition.cur);
}
const rocksdb_writestallcondition_t* rocksdb_writestallinfo_prev(
const rocksdb_writestallinfo_t* info) {
return reinterpret_cast<const rocksdb_writestallcondition_t*>(
&info->rep.condition.prev);
}
@@ -1,61 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'JobInfo metadata simple'
// --header-out
// c_api_gen/c_generated_jobinfo_metadata_subset.h.inc
// --source-out
// c_api_gen/c_generated_jobinfo_metadata_subset.cc.inc
/* JobInfo metadata simple */
extern ROCKSDB_LIBRARY_API size_t rocksdb_compactionjobinfo_input_files_count(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API size_t rocksdb_compactionjobinfo_output_files_count(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compactionjobinfo_elapsed_micros(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compactionjobinfo_num_corrupt_keys(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compactionjobinfo_input_records(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compactionjobinfo_output_records(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compactionjobinfo_total_input_bytes(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compactionjobinfo_total_output_bytes(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API size_t rocksdb_compactionjobinfo_num_input_files(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_compactionjobinfo_num_input_files_at_output_level(
const rocksdb_compactionjobinfo_t* info);
extern ROCKSDB_LIBRARY_API const char* rocksdb_writestallinfo_cf_name(
const rocksdb_writestallinfo_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API const rocksdb_writestallcondition_t*
rocksdb_writestallinfo_cur(const rocksdb_writestallinfo_t* info);
extern ROCKSDB_LIBRARY_API const rocksdb_writestallcondition_t*
rocksdb_writestallinfo_prev(const rocksdb_writestallinfo_t* info);
@@ -1,532 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/auto_simple_bindings.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Sources:
// - include/rocksdb/compaction_job_stats.h
// - include/rocksdb/listener.h
// - include/rocksdb/table_properties.h
// - tools/c_api_gen/auto_simple_bindings_blocklist.json
// - include/rocksdb/c.h
// - tools/c_api_gen/c_base.cc
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/auto_simple_bindings.py
// Output group: Auto-discovered metadata view structs simple
/* TableProperties */
uint64_t rocksdb_table_properties_orig_file_number(
const rocksdb_table_properties_t* props) {
return props->rep.orig_file_number;
}
uint64_t rocksdb_table_properties_data_size(
const rocksdb_table_properties_t* props) {
return props->rep.data_size;
}
uint64_t rocksdb_table_properties_uncompressed_data_size(
const rocksdb_table_properties_t* props) {
return props->rep.uncompressed_data_size;
}
uint64_t rocksdb_table_properties_index_size(
const rocksdb_table_properties_t* props) {
return props->rep.index_size;
}
uint64_t rocksdb_table_properties_index_partitions(
const rocksdb_table_properties_t* props) {
return props->rep.index_partitions;
}
uint64_t rocksdb_table_properties_top_level_index_size(
const rocksdb_table_properties_t* props) {
return props->rep.top_level_index_size;
}
uint64_t rocksdb_table_properties_index_key_is_user_key(
const rocksdb_table_properties_t* props) {
return props->rep.index_key_is_user_key;
}
uint64_t rocksdb_table_properties_index_value_is_delta_encoded(
const rocksdb_table_properties_t* props) {
return props->rep.index_value_is_delta_encoded;
}
uint64_t rocksdb_table_properties_udi_is_primary_index(
const rocksdb_table_properties_t* props) {
return props->rep.udi_is_primary_index;
}
uint64_t rocksdb_table_properties_filter_size(
const rocksdb_table_properties_t* props) {
return props->rep.filter_size;
}
uint64_t rocksdb_table_properties_raw_key_size(
const rocksdb_table_properties_t* props) {
return props->rep.raw_key_size;
}
uint64_t rocksdb_table_properties_raw_value_size(
const rocksdb_table_properties_t* props) {
return props->rep.raw_value_size;
}
uint64_t rocksdb_table_properties_num_data_blocks(
const rocksdb_table_properties_t* props) {
return props->rep.num_data_blocks;
}
uint64_t rocksdb_table_properties_num_data_blocks_compression_rejected(
const rocksdb_table_properties_t* props) {
return props->rep.num_data_blocks_compression_rejected;
}
uint64_t rocksdb_table_properties_num_data_blocks_compression_bypassed(
const rocksdb_table_properties_t* props) {
return props->rep.num_data_blocks_compression_bypassed;
}
uint64_t rocksdb_table_properties_num_uniform_blocks(
const rocksdb_table_properties_t* props) {
return props->rep.num_uniform_blocks;
}
uint64_t rocksdb_table_properties_num_entries(
const rocksdb_table_properties_t* props) {
return props->rep.num_entries;
}
uint64_t rocksdb_table_properties_num_filter_entries(
const rocksdb_table_properties_t* props) {
return props->rep.num_filter_entries;
}
uint64_t rocksdb_table_properties_num_deletions(
const rocksdb_table_properties_t* props) {
return props->rep.num_deletions;
}
uint64_t rocksdb_table_properties_num_merge_operands(
const rocksdb_table_properties_t* props) {
return props->rep.num_merge_operands;
}
uint64_t rocksdb_table_properties_num_range_deletions(
const rocksdb_table_properties_t* props) {
return props->rep.num_range_deletions;
}
uint64_t rocksdb_table_properties_format_version(
const rocksdb_table_properties_t* props) {
return props->rep.format_version;
}
uint64_t rocksdb_table_properties_fixed_key_len(
const rocksdb_table_properties_t* props) {
return props->rep.fixed_key_len;
}
uint64_t rocksdb_table_properties_column_family_id(
const rocksdb_table_properties_t* props) {
return props->rep.column_family_id;
}
uint64_t rocksdb_table_properties_creation_time(
const rocksdb_table_properties_t* props) {
return props->rep.creation_time;
}
uint64_t rocksdb_table_properties_oldest_key_time(
const rocksdb_table_properties_t* props) {
return props->rep.oldest_key_time;
}
uint64_t rocksdb_table_properties_newest_key_time(
const rocksdb_table_properties_t* props) {
return props->rep.newest_key_time;
}
uint64_t rocksdb_table_properties_file_creation_time(
const rocksdb_table_properties_t* props) {
return props->rep.file_creation_time;
}
uint64_t rocksdb_table_properties_slow_compression_estimated_data_size(
const rocksdb_table_properties_t* props) {
return props->rep.slow_compression_estimated_data_size;
}
uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
const rocksdb_table_properties_t* props) {
return props->rep.fast_compression_estimated_data_size;
}
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props) {
return props->rep.external_sst_file_global_seqno_offset;
}
uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props) {
return props->rep.tail_start_offset;
}
uint64_t rocksdb_table_properties_user_defined_timestamps_persisted(
const rocksdb_table_properties_t* props) {
return props->rep.user_defined_timestamps_persisted;
}
uint64_t rocksdb_table_properties_key_largest_seqno(
const rocksdb_table_properties_t* props) {
return props->rep.key_largest_seqno;
}
uint64_t rocksdb_table_properties_key_smallest_seqno(
const rocksdb_table_properties_t* props) {
return props->rep.key_smallest_seqno;
}
uint64_t rocksdb_table_properties_data_block_restart_interval(
const rocksdb_table_properties_t* props) {
return props->rep.data_block_restart_interval;
}
uint64_t rocksdb_table_properties_index_block_restart_interval(
const rocksdb_table_properties_t* props) {
return props->rep.index_block_restart_interval;
}
uint64_t rocksdb_table_properties_separate_key_value_in_data_block(
const rocksdb_table_properties_t* props) {
return props->rep.separate_key_value_in_data_block;
}
uint64_t rocksdb_table_properties_lsm_info_at_creation(
const rocksdb_table_properties_t* props) {
return props->rep.lsm_info_at_creation;
}
const char* rocksdb_table_properties_db_id(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.db_id.size();
return props->rep.db_id.data();
}
const char* rocksdb_table_properties_db_session_id(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.db_session_id.size();
return props->rep.db_session_id.data();
}
const char* rocksdb_table_properties_db_host_id(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.db_host_id.size();
return props->rep.db_host_id.data();
}
const char* rocksdb_table_properties_column_family_name(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.column_family_name.size();
return props->rep.column_family_name.data();
}
const char* rocksdb_table_properties_filter_policy_name(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.filter_policy_name.size();
return props->rep.filter_policy_name.data();
}
const char* rocksdb_table_properties_comparator_name(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.comparator_name.size();
return props->rep.comparator_name.data();
}
const char* rocksdb_table_properties_merge_operator_name(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.merge_operator_name.size();
return props->rep.merge_operator_name.data();
}
const char* rocksdb_table_properties_prefix_extractor_name(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.prefix_extractor_name.size();
return props->rep.prefix_extractor_name.data();
}
const char* rocksdb_table_properties_property_collectors_names(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.property_collectors_names.size();
return props->rep.property_collectors_names.data();
}
const char* rocksdb_table_properties_compression_name(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.compression_name.size();
return props->rep.compression_name.data();
}
const char* rocksdb_table_properties_compression_options(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.compression_options.size();
return props->rep.compression_options.data();
}
const char* rocksdb_table_properties_seqno_to_time_mapping(
const rocksdb_table_properties_t* props, size_t* size) {
*size = props->rep.seqno_to_time_mapping.size();
return props->rep.seqno_to_time_mapping.data();
}
/* CompactionJobStats */
uint64_t rocksdb_compaction_job_stats_elapsed_micros(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.elapsed_micros;
}
uint64_t rocksdb_compaction_job_stats_cpu_micros(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.cpu_micros;
}
unsigned char rocksdb_compaction_job_stats_has_accurate_num_input_records(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.has_accurate_num_input_records;
}
uint64_t rocksdb_compaction_job_stats_num_input_records(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_input_records;
}
uint64_t rocksdb_compaction_job_stats_num_blobs_read(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_blobs_read;
}
size_t rocksdb_compaction_job_stats_num_input_files(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_input_files;
}
size_t rocksdb_compaction_job_stats_num_input_files_trivially_moved(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_input_files_trivially_moved;
}
size_t rocksdb_compaction_job_stats_num_input_files_at_output_level(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_input_files_at_output_level;
}
size_t rocksdb_compaction_job_stats_num_filtered_input_files(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_filtered_input_files;
}
size_t rocksdb_compaction_job_stats_num_filtered_input_files_at_output_level(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_filtered_input_files_at_output_level;
}
uint64_t rocksdb_compaction_job_stats_num_output_records(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_output_records;
}
size_t rocksdb_compaction_job_stats_num_output_files(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_output_files;
}
size_t rocksdb_compaction_job_stats_num_output_files_blob(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_output_files_blob;
}
unsigned char rocksdb_compaction_job_stats_is_full_compaction(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.is_full_compaction;
}
unsigned char rocksdb_compaction_job_stats_is_manual_compaction(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.is_manual_compaction;
}
unsigned char rocksdb_compaction_job_stats_is_remote_compaction(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.is_remote_compaction;
}
uint64_t rocksdb_compaction_job_stats_total_input_bytes(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_input_bytes;
}
uint64_t rocksdb_compaction_job_stats_total_blob_bytes_read(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_blob_bytes_read;
}
uint64_t rocksdb_compaction_job_stats_total_output_bytes(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_output_bytes;
}
uint64_t rocksdb_compaction_job_stats_total_output_bytes_blob(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_output_bytes_blob;
}
uint64_t rocksdb_compaction_job_stats_total_skipped_input_bytes(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_skipped_input_bytes;
}
uint64_t rocksdb_compaction_job_stats_num_records_replaced(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_records_replaced;
}
uint64_t rocksdb_compaction_job_stats_total_input_raw_key_bytes(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_input_raw_key_bytes;
}
uint64_t rocksdb_compaction_job_stats_total_input_raw_value_bytes(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.total_input_raw_value_bytes;
}
uint64_t rocksdb_compaction_job_stats_num_input_deletion_records(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_input_deletion_records;
}
uint64_t rocksdb_compaction_job_stats_num_expired_deletion_records(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_expired_deletion_records;
}
uint64_t rocksdb_compaction_job_stats_num_corrupt_keys(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_corrupt_keys;
}
uint64_t rocksdb_compaction_job_stats_file_write_nanos(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.file_write_nanos;
}
uint64_t rocksdb_compaction_job_stats_file_range_sync_nanos(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.file_range_sync_nanos;
}
uint64_t rocksdb_compaction_job_stats_file_fsync_nanos(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.file_fsync_nanos;
}
uint64_t rocksdb_compaction_job_stats_file_prepare_write_nanos(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.file_prepare_write_nanos;
}
const char* rocksdb_compaction_job_stats_smallest_output_key_prefix(
const rocksdb_compaction_job_stats_t* stats, size_t* size) {
*size = stats->rep.smallest_output_key_prefix.size();
return stats->rep.smallest_output_key_prefix.data();
}
const char* rocksdb_compaction_job_stats_largest_output_key_prefix(
const rocksdb_compaction_job_stats_t* stats, size_t* size) {
*size = stats->rep.largest_output_key_prefix.size();
return stats->rep.largest_output_key_prefix.data();
}
uint64_t rocksdb_compaction_job_stats_num_single_del_fallthru(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_single_del_fallthru;
}
uint64_t rocksdb_compaction_job_stats_num_single_del_mismatch(
const rocksdb_compaction_job_stats_t* stats) {
return stats->rep.num_single_del_mismatch;
}
/* CompactionFileInfo */
int rocksdb_compaction_file_info_level(
const rocksdb_compaction_file_info_t* info) {
return info->rep.level;
}
uint64_t rocksdb_compaction_file_info_file_number(
const rocksdb_compaction_file_info_t* info) {
return info->rep.file_number;
}
uint64_t rocksdb_compaction_file_info_oldest_blob_file_number(
const rocksdb_compaction_file_info_t* info) {
return info->rep.oldest_blob_file_number;
}
/* BlobFileAdditionInfo */
const char* rocksdb_blob_file_addition_info_blob_file_path(
const rocksdb_blob_file_addition_info_t* info, size_t* size) {
*size = info->rep.blob_file_path.size();
return info->rep.blob_file_path.data();
}
uint64_t rocksdb_blob_file_addition_info_blob_file_number(
const rocksdb_blob_file_addition_info_t* info) {
return info->rep.blob_file_number;
}
uint64_t rocksdb_blob_file_addition_info_total_blob_count(
const rocksdb_blob_file_addition_info_t* info) {
return info->rep.total_blob_count;
}
uint64_t rocksdb_blob_file_addition_info_total_blob_bytes(
const rocksdb_blob_file_addition_info_t* info) {
return info->rep.total_blob_bytes;
}
/* BlobFileGarbageInfo */
const char* rocksdb_blob_file_garbage_info_blob_file_path(
const rocksdb_blob_file_garbage_info_t* info, size_t* size) {
*size = info->rep.blob_file_path.size();
return info->rep.blob_file_path.data();
}
uint64_t rocksdb_blob_file_garbage_info_blob_file_number(
const rocksdb_blob_file_garbage_info_t* info) {
return info->rep.blob_file_number;
}
uint64_t rocksdb_blob_file_garbage_info_garbage_blob_count(
const rocksdb_blob_file_garbage_info_t* info) {
return info->rep.garbage_blob_count;
}
uint64_t rocksdb_blob_file_garbage_info_garbage_blob_bytes(
const rocksdb_blob_file_garbage_info_t* info) {
return info->rep.garbage_blob_bytes;
}
@@ -1,385 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/auto_simple_bindings.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Sources:
// - include/rocksdb/compaction_job_stats.h
// - include/rocksdb/listener.h
// - include/rocksdb/table_properties.h
// - tools/c_api_gen/auto_simple_bindings_blocklist.json
// - include/rocksdb/c.h
// - tools/c_api_gen/c_base.cc
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/auto_simple_bindings.py
// Output group: Auto-discovered metadata view structs simple
/* TableProperties */
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_orig_file_number(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_data_size(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_uncompressed_data_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_index_size(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_index_partitions(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_top_level_index_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_index_key_is_user_key(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_index_value_is_delta_encoded(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_udi_is_primary_index(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_filter_size(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_raw_key_size(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_raw_value_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_num_data_blocks(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_num_data_blocks_compression_rejected(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_num_data_blocks_compression_bypassed(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_num_uniform_blocks(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_num_entries(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_num_filter_entries(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_num_deletions(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_num_merge_operands(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_num_range_deletions(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_format_version(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_fixed_key_len(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_column_family_id(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_creation_time(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_oldest_key_time(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_newest_key_time(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_file_creation_time(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_slow_compression_estimated_data_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_fast_compression_estimated_data_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_user_defined_timestamps_persisted(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_key_largest_seqno(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_key_smallest_seqno(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_data_block_restart_interval(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_index_block_restart_interval(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_separate_key_value_in_data_block(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_lsm_info_at_creation(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API const char* rocksdb_table_properties_db_id(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char* rocksdb_table_properties_db_session_id(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char* rocksdb_table_properties_db_host_id(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_column_family_name(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_filter_policy_name(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char* rocksdb_table_properties_comparator_name(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_merge_operator_name(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_prefix_extractor_name(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_property_collectors_names(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_compression_name(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_compression_options(
const rocksdb_table_properties_t* props, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_table_properties_seqno_to_time_mapping(
const rocksdb_table_properties_t* props, size_t* size);
/* CompactionJobStats */
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compaction_job_stats_elapsed_micros(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compaction_job_stats_cpu_micros(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_compaction_job_stats_has_accurate_num_input_records(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_input_records(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compaction_job_stats_num_blobs_read(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t rocksdb_compaction_job_stats_num_input_files(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_compaction_job_stats_num_input_files_trivially_moved(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_compaction_job_stats_num_input_files_at_output_level(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_compaction_job_stats_num_filtered_input_files(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_compaction_job_stats_num_filtered_input_files_at_output_level(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_output_records(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t rocksdb_compaction_job_stats_num_output_files(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_compaction_job_stats_num_output_files_blob(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_compaction_job_stats_is_full_compaction(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_compaction_job_stats_is_manual_compaction(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_compaction_job_stats_is_remote_compaction(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_input_bytes(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_blob_bytes_read(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_output_bytes(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_output_bytes_blob(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_skipped_input_bytes(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_records_replaced(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_input_raw_key_bytes(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_total_input_raw_value_bytes(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_input_deletion_records(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_expired_deletion_records(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_corrupt_keys(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_file_write_nanos(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_file_range_sync_nanos(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_file_fsync_nanos(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_file_prepare_write_nanos(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_compaction_job_stats_smallest_output_key_prefix(
const rocksdb_compaction_job_stats_t* stats, size_t* size);
extern ROCKSDB_LIBRARY_API const char*
rocksdb_compaction_job_stats_largest_output_key_prefix(
const rocksdb_compaction_job_stats_t* stats, size_t* size);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_single_del_fallthru(
const rocksdb_compaction_job_stats_t* stats);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_job_stats_num_single_del_mismatch(
const rocksdb_compaction_job_stats_t* stats);
/* CompactionFileInfo */
extern ROCKSDB_LIBRARY_API int rocksdb_compaction_file_info_level(
const rocksdb_compaction_file_info_t* info);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_compaction_file_info_file_number(
const rocksdb_compaction_file_info_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_compaction_file_info_oldest_blob_file_number(
const rocksdb_compaction_file_info_t* info);
/* BlobFileAdditionInfo */
extern ROCKSDB_LIBRARY_API const char*
rocksdb_blob_file_addition_info_blob_file_path(
const rocksdb_blob_file_addition_info_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_blob_file_addition_info_blob_file_number(
const rocksdb_blob_file_addition_info_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_blob_file_addition_info_total_blob_count(
const rocksdb_blob_file_addition_info_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_blob_file_addition_info_total_blob_bytes(
const rocksdb_blob_file_addition_info_t* info);
/* BlobFileGarbageInfo */
extern ROCKSDB_LIBRARY_API const char*
rocksdb_blob_file_garbage_info_blob_file_path(
const rocksdb_blob_file_garbage_info_t* info, size_t* size);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_blob_file_garbage_info_blob_file_number(
const rocksdb_blob_file_garbage_info_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_blob_file_garbage_info_garbage_blob_count(
const rocksdb_blob_file_garbage_info_t* info);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_blob_file_garbage_info_garbage_blob_bytes(
const rocksdb_blob_file_garbage_info_t* info);
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,88 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'Options simple'
// --header-out
// c_api_gen/c_generated_options_subset.h.inc
// --source-out
// c_api_gen/c_generated_options_subset.cc.inc
/* Options simple */
void rocksdb_options_set_compression_options_zstd_max_train_bytes(
rocksdb_options_t* opt, int zstd_max_train_bytes) {
opt->rep.compression_opts.zstd_max_train_bytes = zstd_max_train_bytes;
}
int rocksdb_options_get_compression_options_zstd_max_train_bytes(
rocksdb_options_t* opt) {
return opt->rep.compression_opts.zstd_max_train_bytes;
}
void rocksdb_options_set_compression_options_use_zstd_dict_trainer(
rocksdb_options_t* opt, unsigned char use_zstd_dict_trainer) {
opt->rep.compression_opts.use_zstd_dict_trainer = use_zstd_dict_trainer;
}
unsigned char rocksdb_options_get_compression_options_use_zstd_dict_trainer(
rocksdb_options_t* opt) {
return opt->rep.compression_opts.use_zstd_dict_trainer;
}
void rocksdb_options_set_compression_options_parallel_threads(
rocksdb_options_t* opt, int value) {
opt->rep.compression_opts.parallel_threads = value;
}
int rocksdb_options_get_compression_options_parallel_threads(
rocksdb_options_t* opt) {
return opt->rep.compression_opts.parallel_threads;
}
void rocksdb_options_set_compression_options_max_dict_buffer_bytes(
rocksdb_options_t* opt, uint64_t max_dict_buffer_bytes) {
opt->rep.compression_opts.max_dict_buffer_bytes = max_dict_buffer_bytes;
}
uint64_t rocksdb_options_get_compression_options_max_dict_buffer_bytes(
rocksdb_options_t* opt) {
return opt->rep.compression_opts.max_dict_buffer_bytes;
}
unsigned char
rocksdb_options_get_bottommost_compression_options_use_zstd_dict_trainer(
rocksdb_options_t* opt) {
return opt->rep.bottommost_compression_opts.use_zstd_dict_trainer;
}
void rocksdb_options_set_use_fsync(rocksdb_options_t* opt, int use_fsync) {
opt->rep.use_fsync = use_fsync;
}
int rocksdb_options_get_use_fsync(rocksdb_options_t* opt) {
return opt->rep.use_fsync;
}
void rocksdb_options_set_disable_auto_compactions(rocksdb_options_t* opt,
int disable) {
opt->rep.disable_auto_compactions = disable;
}
void rocksdb_options_set_optimize_filters_for_hits(rocksdb_options_t* opt,
int v) {
opt->rep.optimize_filters_for_hits = v;
}
void rocksdb_options_set_report_bg_io_stats(rocksdb_options_t* opt, int v) {
opt->rep.report_bg_io_stats = v;
}
@@ -1,71 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'Options simple'
// --header-out
// c_api_gen/c_generated_options_subset.h.inc
// --source-out
// c_api_gen/c_generated_options_subset.cc.inc
/* Options simple */
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_compression_options_zstd_max_train_bytes(
rocksdb_options_t* opt, int zstd_max_train_bytes);
extern ROCKSDB_LIBRARY_API int
rocksdb_options_get_compression_options_zstd_max_train_bytes(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_compression_options_use_zstd_dict_trainer(
rocksdb_options_t* opt, unsigned char use_zstd_dict_trainer);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_compression_options_use_zstd_dict_trainer(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_compression_options_parallel_threads(rocksdb_options_t* opt,
int value);
extern ROCKSDB_LIBRARY_API int
rocksdb_options_get_compression_options_parallel_threads(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_compression_options_max_dict_buffer_bytes(
rocksdb_options_t* opt, uint64_t max_dict_buffer_bytes);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_options_get_compression_options_max_dict_buffer_bytes(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_bottommost_compression_options_use_zstd_dict_trainer(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_fsync(
rocksdb_options_t* opt, int use_fsync);
extern ROCKSDB_LIBRARY_API int rocksdb_options_get_use_fsync(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_disable_auto_compactions(
rocksdb_options_t* opt, int disable);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_optimize_filters_for_hits(
rocksdb_options_t* opt, int v);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_report_bg_io_stats(
rocksdb_options_t* opt, int v);
@@ -1,240 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/auto_simple_bindings.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Sources:
// - include/rocksdb/options.h
// - tools/c_api_gen/auto_simple_bindings_blocklist.json
// - include/rocksdb/c.h
// - tools/c_api_gen/c_base.cc
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/auto_simple_bindings.py
// Output group: Auto-discovered ReadOptions simple
/* ReadOptions */
void rocksdb_readoptions_set_deadline(rocksdb_readoptions_t* opt,
uint64_t microseconds) {
opt->rep.deadline = std::chrono::microseconds(microseconds);
}
uint64_t rocksdb_readoptions_get_deadline(rocksdb_readoptions_t* opt) {
return opt->rep.deadline.count();
}
void rocksdb_readoptions_set_io_timeout(rocksdb_readoptions_t* opt,
uint64_t microseconds) {
opt->rep.io_timeout = std::chrono::microseconds(microseconds);
}
uint64_t rocksdb_readoptions_get_io_timeout(rocksdb_readoptions_t* opt) {
return opt->rep.io_timeout.count();
}
void rocksdb_readoptions_set_read_tier(rocksdb_readoptions_t* opt, int v) {
opt->rep.read_tier = static_cast<decltype(opt->rep.read_tier)>(v);
}
int rocksdb_readoptions_get_read_tier(rocksdb_readoptions_t* opt) {
return static_cast<int>(opt->rep.read_tier);
}
void rocksdb_readoptions_set_rate_limiter_priority(rocksdb_readoptions_t* opt,
int v) {
opt->rep.rate_limiter_priority =
static_cast<decltype(opt->rep.rate_limiter_priority)>(v);
}
int rocksdb_readoptions_get_rate_limiter_priority(rocksdb_readoptions_t* opt) {
return static_cast<int>(opt->rep.rate_limiter_priority);
}
void rocksdb_readoptions_set_value_size_soft_limit(rocksdb_readoptions_t* opt,
uint64_t v) {
opt->rep.value_size_soft_limit = v;
}
uint64_t rocksdb_readoptions_get_value_size_soft_limit(
rocksdb_readoptions_t* opt) {
return opt->rep.value_size_soft_limit;
}
void rocksdb_readoptions_set_verify_checksums(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.verify_checksums = v;
}
unsigned char rocksdb_readoptions_get_verify_checksums(
rocksdb_readoptions_t* opt) {
return opt->rep.verify_checksums;
}
void rocksdb_readoptions_set_fill_cache(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.fill_cache = v;
}
unsigned char rocksdb_readoptions_get_fill_cache(rocksdb_readoptions_t* opt) {
return opt->rep.fill_cache;
}
void rocksdb_readoptions_set_ignore_range_deletions(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.ignore_range_deletions = v;
}
unsigned char rocksdb_readoptions_get_ignore_range_deletions(
rocksdb_readoptions_t* opt) {
return opt->rep.ignore_range_deletions;
}
void rocksdb_readoptions_set_async_io(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.async_io = v;
}
unsigned char rocksdb_readoptions_get_async_io(rocksdb_readoptions_t* opt) {
return opt->rep.async_io;
}
void rocksdb_readoptions_set_optimize_multiget_for_io(
rocksdb_readoptions_t* opt, unsigned char v) {
opt->rep.optimize_multiget_for_io = v;
}
unsigned char rocksdb_readoptions_get_optimize_multiget_for_io(
rocksdb_readoptions_t* opt) {
return opt->rep.optimize_multiget_for_io;
}
void rocksdb_readoptions_set_readahead_size(rocksdb_readoptions_t* opt,
size_t v) {
opt->rep.readahead_size = v;
}
size_t rocksdb_readoptions_get_readahead_size(rocksdb_readoptions_t* opt) {
return opt->rep.readahead_size;
}
void rocksdb_readoptions_set_max_skippable_internal_keys(
rocksdb_readoptions_t* opt, uint64_t v) {
opt->rep.max_skippable_internal_keys = v;
}
uint64_t rocksdb_readoptions_get_max_skippable_internal_keys(
rocksdb_readoptions_t* opt) {
return opt->rep.max_skippable_internal_keys;
}
void rocksdb_readoptions_set_tailing(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.tailing = v;
}
unsigned char rocksdb_readoptions_get_tailing(rocksdb_readoptions_t* opt) {
return opt->rep.tailing;
}
void rocksdb_readoptions_set_total_order_seek(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.total_order_seek = v;
}
unsigned char rocksdb_readoptions_get_total_order_seek(
rocksdb_readoptions_t* opt) {
return opt->rep.total_order_seek;
}
void rocksdb_readoptions_set_auto_prefix_mode(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.auto_prefix_mode = v;
}
unsigned char rocksdb_readoptions_get_auto_prefix_mode(
rocksdb_readoptions_t* opt) {
return opt->rep.auto_prefix_mode;
}
void rocksdb_readoptions_set_prefix_same_as_start(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.prefix_same_as_start = v;
}
unsigned char rocksdb_readoptions_get_prefix_same_as_start(
rocksdb_readoptions_t* opt) {
return opt->rep.prefix_same_as_start;
}
void rocksdb_readoptions_set_pin_data(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.pin_data = v;
}
unsigned char rocksdb_readoptions_get_pin_data(rocksdb_readoptions_t* opt) {
return opt->rep.pin_data;
}
void rocksdb_readoptions_set_adaptive_readahead(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.adaptive_readahead = v;
}
unsigned char rocksdb_readoptions_get_adaptive_readahead(
rocksdb_readoptions_t* opt) {
return opt->rep.adaptive_readahead;
}
void rocksdb_readoptions_set_background_purge_on_iterator_cleanup(
rocksdb_readoptions_t* opt, unsigned char v) {
opt->rep.background_purge_on_iterator_cleanup = v;
}
unsigned char rocksdb_readoptions_get_background_purge_on_iterator_cleanup(
rocksdb_readoptions_t* opt) {
return opt->rep.background_purge_on_iterator_cleanup;
}
void rocksdb_readoptions_set_auto_readahead_size(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.auto_readahead_size = v;
}
unsigned char rocksdb_readoptions_get_auto_readahead_size(
rocksdb_readoptions_t* opt) {
return opt->rep.auto_readahead_size;
}
void rocksdb_readoptions_set_allow_unprepared_value(rocksdb_readoptions_t* opt,
unsigned char v) {
opt->rep.allow_unprepared_value = v;
}
unsigned char rocksdb_readoptions_get_allow_unprepared_value(
rocksdb_readoptions_t* opt) {
return opt->rep.allow_unprepared_value;
}
void rocksdb_readoptions_set_auto_refresh_iterator_with_snapshot(
rocksdb_readoptions_t* opt, unsigned char v) {
opt->rep.auto_refresh_iterator_with_snapshot = v;
}
unsigned char rocksdb_readoptions_get_auto_refresh_iterator_with_snapshot(
rocksdb_readoptions_t* opt) {
return opt->rep.auto_refresh_iterator_with_snapshot;
}
void rocksdb_readoptions_set_io_activity(rocksdb_readoptions_t* opt, int v) {
opt->rep.io_activity = static_cast<decltype(opt->rep.io_activity)>(v);
}
int rocksdb_readoptions_get_io_activity(rocksdb_readoptions_t* opt) {
return static_cast<int>(opt->rep.io_activity);
}
@@ -1,165 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/auto_simple_bindings.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Sources:
// - include/rocksdb/options.h
// - tools/c_api_gen/auto_simple_bindings_blocklist.json
// - include/rocksdb/c.h
// - tools/c_api_gen/c_base.cc
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/auto_simple_bindings.py
// Output group: Auto-discovered ReadOptions simple
/* ReadOptions */
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_deadline(
rocksdb_readoptions_t* opt, uint64_t microseconds);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_readoptions_get_deadline(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_io_timeout(
rocksdb_readoptions_t* opt, uint64_t microseconds);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_readoptions_get_io_timeout(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_read_tier(
rocksdb_readoptions_t* opt, int v);
extern ROCKSDB_LIBRARY_API int rocksdb_readoptions_get_read_tier(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_rate_limiter_priority(
rocksdb_readoptions_t* opt, int v);
extern ROCKSDB_LIBRARY_API int rocksdb_readoptions_get_rate_limiter_priority(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_value_size_soft_limit(
rocksdb_readoptions_t* opt, uint64_t v);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_readoptions_get_value_size_soft_limit(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_verify_checksums(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_verify_checksums(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_fill_cache(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_readoptions_get_fill_cache(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_ignore_range_deletions(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_ignore_range_deletions(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_async_io(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_readoptions_get_async_io(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_readoptions_set_optimize_multiget_for_io(rocksdb_readoptions_t* opt,
unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_optimize_multiget_for_io(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_readahead_size(
rocksdb_readoptions_t* opt, size_t v);
extern ROCKSDB_LIBRARY_API size_t
rocksdb_readoptions_get_readahead_size(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_readoptions_set_max_skippable_internal_keys(rocksdb_readoptions_t* opt,
uint64_t v);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_readoptions_get_max_skippable_internal_keys(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_tailing(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_readoptions_get_tailing(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_total_order_seek(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_total_order_seek(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_auto_prefix_mode(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_auto_prefix_mode(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_prefix_same_as_start(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_prefix_same_as_start(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_pin_data(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_readoptions_get_pin_data(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_adaptive_readahead(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_adaptive_readahead(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_readoptions_set_background_purge_on_iterator_cleanup(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_background_purge_on_iterator_cleanup(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_auto_readahead_size(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_auto_readahead_size(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_allow_unprepared_value(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_allow_unprepared_value(rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_readoptions_set_auto_refresh_iterator_with_snapshot(
rocksdb_readoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_readoptions_get_auto_refresh_iterator_with_snapshot(
rocksdb_readoptions_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_io_activity(
rocksdb_readoptions_t* opt, int v);
extern ROCKSDB_LIBRARY_API int rocksdb_readoptions_get_io_activity(
rocksdb_readoptions_t* opt);
File diff suppressed because it is too large. Load diff
-375
View File
@@ -1,375 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'DB data operations'
// --section 'WriteBatch' --section 'Transaction'
// --section 'TransactionDB simple'
// --header-out
// c_api_gen/c_generated_subset.h.inc
// --source-out
// c_api_gen/c_generated_subset.cc.inc
/* DB data operations */
void rocksdb_put(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* val, size_t vallen,
char** errptr) {
SaveError(errptr,
db->rep->Put(options->rep, Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_put_cf(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* val,
size_t vallen, char** errptr) {
SaveError(errptr, db->rep->Put(options->rep, column_family->rep,
Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_delete(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, Slice(key, keylen)));
}
void rocksdb_delete_cf(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, column_family->rep,
Slice(key, keylen)));
}
void rocksdb_merge(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* val,
size_t vallen, char** errptr) {
SaveError(errptr, db->rep->Merge(options->rep, Slice(key, keylen),
Slice(val, vallen)));
}
void rocksdb_merge_cf(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* val,
size_t vallen, char** errptr) {
SaveError(errptr, db->rep->Merge(options->rep, column_family->rep,
Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_write(rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr) {
SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
}
void rocksdb_put_with_ts(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* ts,
size_t tslen, const char* val, size_t vallen,
char** errptr) {
SaveError(errptr, db->rep->Put(options->rep, Slice(key, keylen),
Slice(ts, tslen), Slice(val, vallen)));
}
void rocksdb_put_cf_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* ts,
size_t tslen, const char* val, size_t vallen,
char** errptr) {
SaveError(errptr,
db->rep->Put(options->rep, column_family->rep, Slice(key, keylen),
Slice(ts, tslen), Slice(val, vallen)));
}
void rocksdb_delete_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, const char* ts,
size_t tslen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, Slice(key, keylen),
Slice(ts, tslen)));
}
void rocksdb_delete_cf_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, const char* ts,
size_t tslen, char** errptr) {
SaveError(errptr, db->rep->Delete(options->rep, column_family->rep,
Slice(key, keylen), Slice(ts, tslen)));
}
void rocksdb_singledelete(rocksdb_t* db, const rocksdb_writeoptions_t* options,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->SingleDelete(options->rep, Slice(key, keylen)));
}
void rocksdb_singledelete_cf(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen, char** errptr) {
SaveError(errptr, db->rep->SingleDelete(options->rep, column_family->rep,
Slice(key, keylen)));
}
void rocksdb_singledelete_with_ts(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
const char* key, size_t keylen,
const char* ts, size_t tslen, char** errptr) {
SaveError(errptr, db->rep->SingleDelete(options->rep, Slice(key, keylen),
Slice(ts, tslen)));
}
void rocksdb_singledelete_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr) {
SaveError(errptr,
db->rep->SingleDelete(options->rep, column_family->rep,
Slice(key, keylen), Slice(ts, tslen)));
}
void rocksdb_delete_range_cf(rocksdb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* start_key, size_t start_key_len,
const char* end_key, size_t end_key_len,
char** errptr) {
SaveError(errptr, db->rep->DeleteRange(options->rep, column_family->rep,
Slice(start_key, start_key_len),
Slice(end_key, end_key_len)));
}
void rocksdb_flush(rocksdb_t* db, const rocksdb_flushoptions_t* options,
char** errptr) {
SaveError(errptr, db->rep->Flush(options->rep));
}
void rocksdb_flush_cf(rocksdb_t* db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family,
char** errptr) {
SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
}
void rocksdb_flush_wal(rocksdb_t* db, unsigned char sync, char** errptr) {
SaveError(errptr, db->rep->FlushWAL(sync));
}
void rocksdb_pause_background_work(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->PauseBackgroundWork());
}
void rocksdb_continue_background_work(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->ContinueBackgroundWork());
}
void rocksdb_disable_file_deletions(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->DisableFileDeletions());
}
void rocksdb_enable_file_deletions(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->EnableFileDeletions());
}
void rocksdb_verify_checksum(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->VerifyChecksum());
}
void rocksdb_verify_file_checksums(rocksdb_t* db, char** errptr) {
SaveError(errptr, db->rep->VerifyFileChecksums(ReadOptions()));
}
void rocksdb_destroy_db(const rocksdb_options_t* options, const char* name,
char** errptr) {
SaveError(errptr, DestroyDB(name, options->rep));
}
void rocksdb_repair_db(const rocksdb_options_t* options, const char* name,
char** errptr) {
SaveError(errptr, RepairDB(name, options->rep));
}
/* WriteBatch */
void rocksdb_writebatch_clear(rocksdb_writebatch_t* b) { b->rep.Clear(); }
void rocksdb_writebatch_put(rocksdb_writebatch_t* b, const char* key,
size_t klen, const char* val, size_t vlen) {
b->rep.Put(Slice(key, klen), Slice(val, vlen));
}
void rocksdb_writebatch_put_cf(rocksdb_writebatch_t* b,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val,
size_t vlen) {
b->rep.Put(column_family->rep, Slice(key, klen), Slice(val, vlen));
}
void rocksdb_writebatch_delete(rocksdb_writebatch_t* b, const char* key,
size_t klen) {
b->rep.Delete(Slice(key, klen));
}
void rocksdb_writebatch_put_log_data(rocksdb_writebatch_t* b, const char* blob,
size_t len) {
b->rep.PutLogData(Slice(blob, len));
}
void rocksdb_writebatch_set_save_point(rocksdb_writebatch_t* b) {
b->rep.SetSavePoint();
}
void rocksdb_writebatch_rollback_to_save_point(rocksdb_writebatch_t* b,
char** errptr) {
SaveError(errptr, b->rep.RollbackToSavePoint());
}
void rocksdb_writebatch_pop_save_point(rocksdb_writebatch_t* b, char** errptr) {
SaveError(errptr, b->rep.PopSavePoint());
}
void rocksdb_writebatch_verify_checksum(rocksdb_writebatch_t* b,
char** errptr) {
SaveError(errptr, b->rep.VerifyChecksum());
}
/* Transaction */
void rocksdb_transaction_put(rocksdb_transaction_t* txn, const char* key,
size_t klen, const char* val, size_t vlen,
char** errptr) {
SaveError(errptr, txn->rep->Put(Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transaction_put_cf(rocksdb_transaction_t* txn,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr, txn->rep->Put(column_family->rep, Slice(key, klen),
Slice(val, vlen)));
}
void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key,
size_t klen, const char* val, size_t vlen,
char** errptr) {
SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transaction_merge_cf(rocksdb_transaction_t* txn,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr, txn->rep->Merge(column_family->rep, Slice(key, klen),
Slice(val, vlen)));
}
void rocksdb_transaction_delete(rocksdb_transaction_t* txn, const char* key,
size_t klen, char** errptr) {
SaveError(errptr, txn->rep->Delete(Slice(key, klen)));
}
void rocksdb_transaction_delete_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, char** errptr) {
SaveError(errptr, txn->rep->Delete(column_family->rep, Slice(key, klen)));
}
void rocksdb_transaction_commit(rocksdb_transaction_t* txn, char** errptr) {
SaveError(errptr, txn->rep->Commit());
}
void rocksdb_transaction_rollback(rocksdb_transaction_t* txn, char** errptr) {
SaveError(errptr, txn->rep->Rollback());
}
void rocksdb_transaction_put_log_data(rocksdb_transaction_t* txn,
const char* blob, size_t len) {
txn->rep->PutLogData(Slice(blob, len));
}
void rocksdb_transaction_set_savepoint(rocksdb_transaction_t* txn) {
txn->rep->SetSavePoint();
}
void rocksdb_transaction_rollback_to_savepoint(rocksdb_transaction_t* txn,
char** errptr) {
SaveError(errptr, txn->rep->RollbackToSavePoint());
}
/* TransactionDB simple */
void rocksdb_transactiondb_put(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr,
txn_db->rep->Put(options->rep, Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen,
const char* val, size_t vallen,
char** errptr) {
SaveError(errptr, txn_db->rep->Put(options->rep, column_family->rep,
Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_transactiondb_write(rocksdb_transactiondb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr) {
SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
}
void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr, txn_db->rep->Merge(options->rep, Slice(key, klen),
Slice(val, vlen)));
}
void rocksdb_transactiondb_merge_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
const char* val, size_t vlen, char** errptr) {
SaveError(errptr, txn_db->rep->Merge(options->rep, column_family->rep,
Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transactiondb_delete(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
const char* key, size_t klen, char** errptr) {
SaveError(errptr, txn_db->rep->Delete(options->rep, Slice(key, klen)));
}
void rocksdb_transactiondb_delete_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr) {
SaveError(errptr, txn_db->rep->Delete(options->rep, column_family->rep,
Slice(key, keylen)));
}
void rocksdb_transactiondb_flush_wal(rocksdb_transactiondb_t* txn_db,
unsigned char sync, char** errptr) {
SaveError(errptr, txn_db->rep->FlushWAL(sync));
}
void rocksdb_transactiondb_flush(rocksdb_transactiondb_t* txn_db,
const rocksdb_flushoptions_t* options,
char** errptr) {
SaveError(errptr, txn_db->rep->Flush(options->rep));
}
void rocksdb_transactiondb_flush_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family, char** errptr) {
SaveError(errptr, txn_db->rep->Flush(options->rep, column_family->rep));
}
-249
View File
@@ -1,249 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'DB data operations'
// --section 'WriteBatch' --section 'Transaction'
// --section 'TransactionDB simple'
// --header-out
// c_api_gen/c_generated_subset.h.inc
// --source-out
// c_api_gen/c_generated_subset.cc.inc
/* DB data operations */
extern ROCKSDB_LIBRARY_API void rocksdb_put(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_put_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_merge(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_merge_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_write(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_put_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* ts, size_t tslen, const char* val, size_t vallen,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_put_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, const char* val, size_t vallen,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_singledelete_cf_with_ts(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* ts, size_t tslen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_delete_range_cf(
rocksdb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* start_key,
size_t start_key_len, const char* end_key, size_t end_key_len,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_flush(
rocksdb_t* db, const rocksdb_flushoptions_t* options, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_flush_cf(
rocksdb_t* db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_flush_wal(rocksdb_t* db,
unsigned char sync,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_pause_background_work(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_continue_background_work(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_disable_file_deletions(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_enable_file_deletions(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_verify_checksum(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_verify_file_checksums(rocksdb_t* db,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_destroy_db(
const rocksdb_options_t* options, const char* name, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_repair_db(
const rocksdb_options_t* options, const char* name, char** errptr);
/* WriteBatch */
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_clear(
rocksdb_writebatch_t* b);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put(rocksdb_writebatch_t* b,
const char* key,
size_t klen,
const char* val,
size_t vlen);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_cf(
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val, size_t vlen);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete(
rocksdb_writebatch_t* b, const char* key, size_t klen);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_log_data(
rocksdb_writebatch_t* b, const char* blob, size_t len);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_set_save_point(
rocksdb_writebatch_t* b);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_rollback_to_save_point(
rocksdb_writebatch_t* b, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_pop_save_point(
rocksdb_writebatch_t* b, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_verify_checksum(
rocksdb_writebatch_t* b, char** errptr);
/* Transaction */
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put(
rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge(
rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete(
rocksdb_transaction_t* txn, const char* key, size_t klen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_commit(
rocksdb_transaction_t* txn, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback(
rocksdb_transaction_t* txn, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put_log_data(
rocksdb_transaction_t* txn, const char* blob, size_t len);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_set_savepoint(
rocksdb_transaction_t* txn);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback_to_savepoint(
rocksdb_transaction_t* txn, char** errptr);
/* TransactionDB simple */
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_put(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_put_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_write(
rocksdb_transactiondb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_delete(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
const char* key, size_t klen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_delete_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_flush_wal(
rocksdb_transactiondb_t* txn_db, unsigned char sync, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_flush(
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_flush_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family, char** errptr);
@@ -1,81 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'Transaction'
// --header-out
// c_api_gen/c_generated_transaction_subset.h.inc
// --source-out
// c_api_gen/c_generated_transaction_subset.cc.inc
/* Transaction */
void rocksdb_transaction_put(rocksdb_transaction_t* txn, const char* key,
size_t klen, const char* val, size_t vlen,
char** errptr) {
SaveError(errptr, txn->rep->Put(Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transaction_put_cf(rocksdb_transaction_t* txn,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr, txn->rep->Put(column_family->rep, Slice(key, klen),
Slice(val, vlen)));
}
void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key,
size_t klen, const char* val, size_t vlen,
char** errptr) {
SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transaction_merge_cf(rocksdb_transaction_t* txn,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr, txn->rep->Merge(column_family->rep, Slice(key, klen),
Slice(val, vlen)));
}
void rocksdb_transaction_delete(rocksdb_transaction_t* txn, const char* key,
size_t klen, char** errptr) {
SaveError(errptr, txn->rep->Delete(Slice(key, klen)));
}
void rocksdb_transaction_delete_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, char** errptr) {
SaveError(errptr, txn->rep->Delete(column_family->rep, Slice(key, klen)));
}
void rocksdb_transaction_commit(rocksdb_transaction_t* txn, char** errptr) {
SaveError(errptr, txn->rep->Commit());
}
void rocksdb_transaction_rollback(rocksdb_transaction_t* txn, char** errptr) {
SaveError(errptr, txn->rep->Rollback());
}
void rocksdb_transaction_put_log_data(rocksdb_transaction_t* txn,
const char* blob, size_t len) {
txn->rep->PutLogData(Slice(blob, len));
}
void rocksdb_transaction_set_savepoint(rocksdb_transaction_t* txn) {
txn->rep->SetSavePoint();
}
void rocksdb_transaction_rollback_to_savepoint(rocksdb_transaction_t* txn,
char** errptr) {
SaveError(errptr, txn->rep->RollbackToSavePoint());
}
@@ -1,58 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'Transaction'
// --header-out
// c_api_gen/c_generated_transaction_subset.h.inc
// --source-out
// c_api_gen/c_generated_transaction_subset.cc.inc
/* Transaction */
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put(
rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge(
rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete(
rocksdb_transaction_t* txn, const char* key, size_t klen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete_cf(
rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_commit(
rocksdb_transaction_t* txn, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback(
rocksdb_transaction_t* txn, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put_log_data(
rocksdb_transaction_t* txn, const char* blob, size_t len);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_set_savepoint(
rocksdb_transaction_t* txn);
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback_to_savepoint(
rocksdb_transaction_t* txn, char** errptr);
@@ -1,91 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'TransactionDB simple'
// --header-out
// c_api_gen/c_generated_transactiondb_subset.h.inc
// --source-out
// c_api_gen/c_generated_transactiondb_subset.cc.inc
/* TransactionDB simple */
void rocksdb_transactiondb_put(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr,
txn_db->rep->Put(options->rep, Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t keylen,
const char* val, size_t vallen,
char** errptr) {
SaveError(errptr, txn_db->rep->Put(options->rep, column_family->rep,
Slice(key, keylen), Slice(val, vallen)));
}
void rocksdb_transactiondb_write(rocksdb_transactiondb_t* db,
const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr) {
SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
}
void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val,
size_t vlen, char** errptr) {
SaveError(errptr, txn_db->rep->Merge(options->rep, Slice(key, klen),
Slice(val, vlen)));
}
void rocksdb_transactiondb_merge_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
const char* val, size_t vlen, char** errptr) {
SaveError(errptr, txn_db->rep->Merge(options->rep, column_family->rep,
Slice(key, klen), Slice(val, vlen)));
}
void rocksdb_transactiondb_delete(rocksdb_transactiondb_t* txn_db,
const rocksdb_writeoptions_t* options,
const char* key, size_t klen, char** errptr) {
SaveError(errptr, txn_db->rep->Delete(options->rep, Slice(key, klen)));
}
void rocksdb_transactiondb_delete_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr) {
SaveError(errptr, txn_db->rep->Delete(options->rep, column_family->rep,
Slice(key, keylen)));
}
void rocksdb_transactiondb_flush_wal(rocksdb_transactiondb_t* txn_db,
unsigned char sync, char** errptr) {
SaveError(errptr, txn_db->rep->FlushWAL(sync));
}
void rocksdb_transactiondb_flush(rocksdb_transactiondb_t* txn_db,
const rocksdb_flushoptions_t* options,
char** errptr) {
SaveError(errptr, txn_db->rep->Flush(options->rep));
}
void rocksdb_transactiondb_flush_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family, char** errptr) {
SaveError(errptr, txn_db->rep->Flush(options->rep, column_family->rep));
}
@@ -1,62 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'TransactionDB simple'
// --header-out
// c_api_gen/c_generated_transactiondb_subset.h.inc
// --source-out
// c_api_gen/c_generated_transactiondb_subset.cc.inc
/* TransactionDB simple */
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_put(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_put_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, const char* val, size_t vallen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_write(
rocksdb_transactiondb_t* db, const rocksdb_writeoptions_t* options,
rocksdb_writebatch_t* batch, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
const char* val, size_t vlen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_delete(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
const char* key, size_t klen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_delete_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
rocksdb_column_family_handle_t* column_family, const char* key,
size_t keylen, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_flush_wal(
rocksdb_transactiondb_t* txn_db, unsigned char sync, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_flush(
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_flush_cf(
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
rocksdb_column_family_handle_t* column_family, char** errptr);
@@ -1,62 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'WriteBatch'
// --header-out
// c_api_gen/c_generated_writebatch_subset.h.inc
// --source-out
// c_api_gen/c_generated_writebatch_subset.cc.inc
/* WriteBatch */
void rocksdb_writebatch_clear(rocksdb_writebatch_t* b) { b->rep.Clear(); }
void rocksdb_writebatch_put(rocksdb_writebatch_t* b, const char* key,
size_t klen, const char* val, size_t vlen) {
b->rep.Put(Slice(key, klen), Slice(val, vlen));
}
void rocksdb_writebatch_put_cf(rocksdb_writebatch_t* b,
rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val,
size_t vlen) {
b->rep.Put(column_family->rep, Slice(key, klen), Slice(val, vlen));
}
void rocksdb_writebatch_delete(rocksdb_writebatch_t* b, const char* key,
size_t klen) {
b->rep.Delete(Slice(key, klen));
}
void rocksdb_writebatch_put_log_data(rocksdb_writebatch_t* b, const char* blob,
size_t len) {
b->rep.PutLogData(Slice(blob, len));
}
void rocksdb_writebatch_set_save_point(rocksdb_writebatch_t* b) {
b->rep.SetSavePoint();
}
void rocksdb_writebatch_rollback_to_save_point(rocksdb_writebatch_t* b,
char** errptr) {
SaveError(errptr, b->rep.RollbackToSavePoint());
}
void rocksdb_writebatch_pop_save_point(rocksdb_writebatch_t* b, char** errptr) {
SaveError(errptr, b->rep.PopSavePoint());
}
void rocksdb_writebatch_verify_checksum(rocksdb_writebatch_t* b,
char** errptr) {
SaveError(errptr, b->rep.VerifyChecksum());
}
@@ -1,51 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
// @generated
// -----------------------------------------------------------------------------
// Auto-generated by tools/c_api_gen/generate_c_api.py.
// DO NOT EDIT THIS FILE DIRECTLY.
// Source: tools/c_api_gen/spec.json
// -----------------------------------------------------------------------------
// To regenerate this file:
// python3 tools/c_api_gen/generate_c_api.py
// --spec tools/c_api_gen/spec.json --section 'WriteBatch'
// --header-out
// c_api_gen/c_generated_writebatch_subset.h.inc
// --source-out
// c_api_gen/c_generated_writebatch_subset.cc.inc
/* WriteBatch */
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_clear(
rocksdb_writebatch_t* b);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put(rocksdb_writebatch_t* b,
const char* key,
size_t klen,
const char* val,
size_t vlen);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_cf(
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
const char* key, size_t klen, const char* val, size_t vlen);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete(
rocksdb_writebatch_t* b, const char* key, size_t klen);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_log_data(
rocksdb_writebatch_t* b, const char* blob, size_t len);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_set_save_point(
rocksdb_writebatch_t* b);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_rollback_to_save_point(
rocksdb_writebatch_t* b, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_pop_save_point(
rocksdb_writebatch_t* b, char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_verify_checksum(
rocksdb_writebatch_t* b, char** errptr);
-18
View File
@@ -127,24 +127,6 @@ class OffsetableCacheKey : private CacheKey {
return CacheKey(file_num_etc64_, offset_etc64_ ^ offset);
}
// Construct a CacheKey for a record located at byte `offset` within a file in
// which every distinct cached region is at least 5 bytes long. The low two
// offset bits are dropped, which is collision-free under that >= 5-byte (>= 4
// would suffice) guarantee and lets different kinds of records in the same
// file share a single keyspace without colliding.
//
// This is the canonical cache-key scheme for byte-offset records that carry
// the block-based "min 5 byte" guarantee: block-based SST blocks (see
// BlockBasedTable::GetCacheKey) and SimpleGen2Blob records (which always
// include a >= 5-byte trailer; see db/blob/blob_gen2_format.h). Because both
// use this same function, an SST's data blocks and its embedded blob records
// never collide even when the block cache and blob cache are the same cache.
// Keeping a single implementation here avoids a divergence bug that would
// silently alias the two keyspaces.
inline CacheKey WithOffsetForMinSizeRecord(uint64_t offset) const {
return WithOffset(offset >> 2);
}
// The "common prefix" is a shared prefix for all the returned CacheKeys.
// It is specific to the file but the same for all offsets within the file.
static constexpr size_t kCommonPrefixSize = 8;
+1 -1
View File
@@ -3108,7 +3108,7 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
HandleImpl* const arr = array_.Get();
NextWithShift next_with_shift = arr[home].head_next_with_shift.LoadRelaxed();
for (size_t i = 0; !next_with_shift.IsEnd() && i < 10; ++i) {
HandleImpl* h = &arr[next_with_shift.GetNext()];
HandleImpl* h = &arr[next_with_shift.IsEnd()];
// Attempt cheap key match without acquiring a read ref. This could give a
// false positive, which is re-checked after acquiring read ref, or false
// negative, which is re-checked in the full Lookup. Also, this is a
+1 -1
View File
@@ -245,7 +245,7 @@ class ShardedCache : public ShardedCacheBase {
std::unique_ptr<size_t[]> states(new size_t[num_shards]{});
size_t aepl = opts.average_entries_per_lock;
aepl = std::max(aepl, size_t{1});
aepl = std::min(aepl, size_t{1});
bool remaining_work;
do {
+27 -50
View File
@@ -46,7 +46,7 @@ This document provides guidance on how to add new options to RocksDB's public AP
## Pattern 1: Adding a Standard Column Family Option
Example reference: commit `94e65a2e0b4f817aa4bfa4c96cdf867e7980d7bc` (memtable_verify_per_key_checksum_on_seek)
Example reference: commit `94e65a2e0b4f817aa4bfa4c96cdf867e7980d7bc` (memtable_veirfy_per_key_checksum_on_seek)
### Step 1: Define the Option in Public Header
@@ -63,7 +63,7 @@ Add the option with documentation in `AdvancedColumnFamilyOptions` struct:
// operation.
// This option depends on memtable_protection_bytes_per_key to be non zero.
// If memtable_protection_bytes_per_key is zero, no validation is performed.
bool memtable_verify_per_key_checksum_on_seek = false;
bool memtable_veirfy_per_key_checksum_on_seek = false;
```
### Step 2: Add to Internal Options Structs
@@ -74,14 +74,14 @@ Add to `MutableCFOptions` struct (or `ImmutableCFOptions` for immutable options)
```cpp
// In MutableCFOptions constructor from Options:
memtable_verify_per_key_checksum_on_seek(
options.memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek(
options.memtable_veirfy_per_key_checksum_on_seek),
// In MutableCFOptions default constructor:
memtable_verify_per_key_checksum_on_seek(false),
memtable_veirfy_per_key_checksum_on_seek(false),
// In MutableCFOptions struct member declarations:
bool memtable_verify_per_key_checksum_on_seek;
bool memtable_veirfy_per_key_checksum_on_seek;
```
### Step 3: Register for Serialization/Deserialization
@@ -91,9 +91,9 @@ bool memtable_verify_per_key_checksum_on_seek;
Add to the options type info map for serialization:
```cpp
{"memtable_verify_per_key_checksum_on_seek",
{"memtable_veirfy_per_key_checksum_on_seek",
{offsetof(struct MutableCFOptions,
memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
```
@@ -101,8 +101,8 @@ Add to the options type info map for serialization:
Add logging in `MutableCFOptions::Dump()`:
```cpp
ROCKS_LOG_INFO(log, "memtable_verify_per_key_checksum_on_seek: %d",
memtable_verify_per_key_checksum_on_seek);
ROCKS_LOG_INFO(log, "memtable_veirfy_per_key_checksum_on_seek: %d",
memtable_veirfy_per_key_checksum_on_seek);
```
### Step 4: Update Options Helper
@@ -112,8 +112,8 @@ ROCKS_LOG_INFO(log, "memtable_verify_per_key_checksum_on_seek: %d",
Add to `UpdateColumnFamilyOptions()`:
```cpp
cf_opts->memtable_verify_per_key_checksum_on_seek =
moptions.memtable_verify_per_key_checksum_on_seek;
cf_opts->memtable_veirfy_per_key_checksum_on_seek =
moptions.memtable_veirfy_per_key_checksum_on_seek;
```
### Step 5: Add to Options Settable Test
@@ -123,7 +123,7 @@ cf_opts->memtable_verify_per_key_checksum_on_seek =
Add to the test string in `ColumnFamilyOptionsAllFieldsSettable`:
```cpp
"memtable_verify_per_key_checksum_on_seek=1;"
"memtable_veirfy_per_key_checksum_on_seek=1;"
```
### Step 6: Add db_stress Support
@@ -131,23 +131,23 @@ Add to the test string in `ColumnFamilyOptionsAllFieldsSettable`:
**File: `db_stress_tool/db_stress_common.h`**
```cpp
DECLARE_bool(memtable_verify_per_key_checksum_on_seek);
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
```
**File: `db_stress_tool/db_stress_gflags.cc`**
```cpp
DEFINE_bool(
memtable_verify_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_verify_per_key_checksum_on_seek,
"Sets CF option memtable_verify_per_key_checksum_on_seek.");
memtable_veirfy_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_veirfy_per_key_checksum_on_seek,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek.");
```
**File: `db_stress_tool/db_stress_test_base.cc`**
```cpp
options.memtable_verify_per_key_checksum_on_seek =
FLAGS_memtable_verify_per_key_checksum_on_seek;
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
```
### Step 7: Add db_bench Support
@@ -156,12 +156,12 @@ options.memtable_verify_per_key_checksum_on_seek =
```cpp
// Flag definition (near related flags):
DEFINE_bool(memtable_verify_per_key_checksum_on_seek, false,
"Sets CF option memtable_verify_per_key_checksum_on_seek");
DEFINE_bool(memtable_veirfy_per_key_checksum_on_seek, false,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek");
// Apply flag to options (in InitializeOptionsFromFlags or similar):
options.memtable_verify_per_key_checksum_on_seek =
FLAGS_memtable_verify_per_key_checksum_on_seek;
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
```
### Step 8: Add Crash Test Support
@@ -169,7 +169,7 @@ options.memtable_verify_per_key_checksum_on_seek =
**File: `tools/db_crashtest.py`**
```python
"memtable_verify_per_key_checksum_on_seek": lambda: random.choice([0] * 7 + [1]),
"memtable_veirfy_per_key_checksum_on_seek": lambda: random.choice([0] * 7 + [1]),
```
Also add constraint handling in `finalize_and_sanitize()` if needed:
@@ -177,7 +177,7 @@ Also add constraint handling in `finalize_and_sanitize()` if needed:
```python
# only skip list memtable representation supports paranoid memory checks
if dest_params.get("memtablerep") != "skip_list":
dest_params["memtable_verify_per_key_checksum_on_seek"] = 0
dest_params["memtable_veirfy_per_key_checksum_on_seek"] = 0
```
### Step 9: Add Release Note
@@ -185,7 +185,7 @@ if dest_params.get("memtablerep") != "skip_list":
**File: `unreleased_history/new_features/<descriptive_name>.md`**
```markdown
A new flag memtable_verify_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
A new flag memtable_veirfy_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
```
---
@@ -421,30 +421,6 @@ Example reference: commit `429b36c22d76403d275dd0e6877b08d4cea2bc90` (block_alig
If an option already exists but needs C API support:
**First decide whether the option is auto-managed.**
Many simple option fields are no longer maintained by hand in
`include/rocksdb/c.h` and `db/c.cc`. Before adding a manual wrapper:
- Check whether the option belongs to a family handled by
`tools/c_api_gen/auto_simple_bindings.py`.
- If it is a simple scalar/enum/string field in an auto-managed family, run
`python3 tools/c_api_gen/regen_all.py` and let the generator add the C API.
- After regenerating, run
`python3 tools/c_api_gen/verify_generated_up_to_date.py` to confirm the
checked-in generated fragments stay stable.
- Do not edit generated `.inc` files under `c_api_gen/` by hand.
- If the new field is not ready for C API support yet, add an entry to
`tools/c_api_gen/auto_simple_bindings_blocklist.json` with
`"policy": "deferred"` and a concrete reason so the build stays intentional
rather than silently drifting.
- If the field needs custom ownership, callback, vector, or nested-struct
marshalling, keep it manual or move it into `tools/c_api_gen/spec.json`
depending on the API shape.
If the option is not auto-managed, or the C API really is a custom/manual case,
the traditional hand-written pattern still applies:
**File: `db/c.cc`**
```cpp
@@ -533,3 +509,4 @@ Common option types used in serialization:
- [ ] unreleased_history markdown file
- [ ] Java API (for BlockBasedTableOptions)
- [ ] C API (if needed)
+5 -46
View File
@@ -168,18 +168,9 @@ Status YourNewAPI(const YourAPIOptions& /*options*/,
### Step 5: Add C API Bindings
> **Important:** `include/rocksdb/c.h` and `db/c.cc` are `@generated` and must
> NOT be edited by hand — your changes would be overwritten on the next
> regeneration. Choose the right path first (see "Before writing C API code by
> hand" below). For hand-written (manual) wrappers, edit the source templates
> `tools/c_api_gen/c_base.h` (declarations) and `tools/c_api_gen/c_base.cc`
> (implementations), then run `python3 tools/c_api_gen/regen_all.py` to produce
> `c.h` / `c.cc`. The snippets below show the *declaration* and *implementation*
> you would add to those templates.
**Header:** `include/rocksdb/c.h`
**Header (declaration → `tools/c_api_gen/c_base.h`):**
```c
\`\`\`c
// Basic version
extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api(
rocksdb_t* db,
@@ -200,7 +191,7 @@ extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api_opt(
char** errptr);
\`\`\`
**Implementation (→ `tools/c_api_gen/c_base.cc`):**
**Implementation:** `db/c.cc`
\`\`\`cpp
void rocksdb_your_new_api(rocksdb_t* db, const char* start_key,
@@ -226,38 +217,6 @@ void rocksdb_your_new_api_cf(rocksdb_t* db,
}
\`\`\`
**Before writing C API code by hand, choose the right path:**
- **Auto-managed simple struct fields:** If you added a new field to a managed
public struct such as `ReadOptions`, selected option structs, or managed
metadata structs, first check `tools/c_api_gen/auto_simple_bindings.py`.
Simple scalar/enum/string fields should be regenerated with
`python3 tools/c_api_gen/regen_all.py` rather than hand-editing
`include/rocksdb/c.h` / `db/c.cc`.
After regenerating, run
`python3 tools/c_api_gen/verify_generated_up_to_date.py` to confirm the
checked-in generated fragments are stable.
- **Spec-driven wrappers:** If the API is still mechanically generated but needs
an explicit C shape, naming, or `Status`/`char** errptr` policy, add it to
`tools/c_api_gen/spec.json`.
- **Fully manual wrappers:** If the API uses callbacks, ownership transfer,
vectors/maps, open flows, or otherwise irregular marshalling, keep the C API
hand-written.
**Temporary deferral for auto-managed families:**
If a new field lands in an auto-managed family but the C API shape is not ready
yet, add a checked-in entry to
`tools/c_api_gen/auto_simple_bindings_blocklist.json` with:
- `policy: "manual"` when the field is intentionally outside simple auto-gen
- `policy: "deferred"` when the field should be revisited later
- a concrete `reason`
- `tracking_issue` when available
If a field in an auto-managed family is not supported by the generator and is
not covered by the blocklist, regeneration should fail. That is intentional.
**If you have options, also add:**
\`\`\`cpp
@@ -523,8 +482,8 @@ public class YourAPIOptionsTest {
| StackableDB | `include/rocksdb/utilities/stackable_db.h` | ✓ |
| Secondary DB | `db/db_impl/db_impl_secondary.h` | If not supported |
| Compacted DB | `db/db_impl/compacted_db_impl.h` | If not supported |
| C API Header (manual wrappers) | `tools/c_api_gen/c_base.h` → regen → `include/rocksdb/c.h` (`@generated`) | ✓ |
| C API Implementation (manual wrappers) | `tools/c_api_gen/c_base.cc` → regen → `db/c.cc` (`@generated`) | ✓ |
| C API Header | `include/rocksdb/c.h` | ✓ |
| C API Implementation | `db/c.cc` | ✓ |
| Java API | `java/src/main/java/org/rocksdb/RocksDB.java` | ✓ |
| Java Options | `java/src/main/java/org/rocksdb/YourAPIOptions.java` | If needed |
| JNI Implementation | `java/rocksjni/rocksjni.cc` | ✓ |
+15 -57
View File
@@ -4,43 +4,12 @@
Thorough multi-agent code review for RocksDB commits following CLAUDE.md guidelines,
with structured codebase exploration, inter-agent debate, and verification.
## Behavioral Compatibility Review Perspective
Behavior changes are human judgment calls. They must be intentional,
well-documented (including release notes), and researched for potential
downstream production impact. Agents and reviewers must call out concrete gaps,
ambiguity, or adverse impacts in the change's intent, documentation, and
production-impact analysis.
When a diff changes how an existing accepted public or serialized input is
interpreted—including an undocumented, discouraged, or historically ignored
value—reviewers must:
- Compare the old and new observable behavior, including performance and
resource impact.
- Check that the rationale and user-facing documentation clearly describe the
change.
- Identify known direct callers and stored configurations that may depend on
the previous behavior.
- Check for appropriate regression coverage, or a documented reason why such
coverage is not practical.
A behavior change is not automatically a defect. Report a finding when the
intent, documentation, downstream-impact analysis, or mitigation has a concrete
gap or ambiguity that could lead to an unintended compatibility regression.
IMPORTANT — Where the review goes: Your FINAL RESPONSE TEXT is the review.
The PR comment is generated verbatim from your final response, so the
complete, polished review MUST appear there in full. Do NOT end your turn with
a meta-summary such as "the review has been written to review-findings.md" —
paste the entire review as your response.
review-findings.md is a RECOVERY BACKSTOP, not the primary output: after
completing EACH major phase (Setup, Codebase Context, Initial Review, Debate,
Synthesis), append your current findings to it with the Write tool, and after
the final synthesis overwrite it with the complete review. This preserves
partial results if the turn limit interrupts the run (a recovery step reads
this file); it does NOT substitute for outputting the full review as your
final response.
IMPORTANT — Incremental output: After completing EACH major phase (Setup,
Codebase Context, Initial Review, Debate, Synthesis), append your findings
to review-findings.md using the Write tool. This ensures partial results are
saved even if the review is interrupted by the turn limit. After the final
synthesis, write the complete review to review-findings.md, replacing
previous content. Also output the final review as your response text.
## Workflow Steps
@@ -417,8 +386,8 @@ Write findings to `findings-caller-audit.md`.
- Documentation completeness
- Deprecation policy compliance
- Forward compatibility (struct extensibility)
- Apply the Behavioral Compatibility Review Perspective above to changes in
observable semantics and implicit caller contracts.
- **Behavioral compatibility**: Do changed semantics (e.g., kOutOfBound vs
kUnknown) break implicit contracts with callers?
#### Agent: Serialization & Deserialization Reviewer
- Format correctness and versioning
@@ -497,9 +466,7 @@ Team lead synthesizes the debate into a consensus document:
- Include detailed bug analysis (root cause, vulnerable code paths)
- Include suggested fixes
- Note which findings were debated and the outcome
- Output the complete review as your final response text (this becomes the PR
comment verbatim), and also overwrite review-findings.md with the identical
content as a recovery backstop
- Write the complete review to review-findings.md and output as response
**Final report quality rules:**
- The final report must be CLEAN and POLISHED. No stream-of-consciousness,
@@ -516,10 +483,9 @@ Team lead synthesizes the debate into a consensus document:
**REQUIRED output structure (so the PR page stays scrollable):**
Your final response — which becomes the PR comment verbatim — (and the
identical contents of `review-findings.md`) MUST follow this exact structure.
The summary appears first so reviewers can see HIGH findings at a glance;
everything else is hidden behind a `<details>` block.
The final response (and contents of `review-findings.md`) MUST follow this
exact structure. The summary appears first so reviewers can see HIGH findings
at a glance; everything else is hidden behind a `<details>` block.
```markdown
## Summary
@@ -567,9 +533,6 @@ Rules for this structure:
- Do NOT nest a `<details>` inside another `<details>`.
- Do NOT add any text after the closing `</details>` — the comment-builder
appends its own footer.
- Your final response MUST be the review itself in this structure — never a
meta-summary like "the review has been written to review-findings.md". The
PR comment is generated verbatim from your final response.
- If the review is partial (recovery path), still produce the summary block
first, and put whatever was salvaged inside the `<details>` block.
@@ -608,14 +571,9 @@ Rules for this structure:
- [ ] All callers enumerated with parameter range table
## Output Structure
Your **final response text is the primary output** — it becomes the PR comment
verbatim. The files below are working artifacts and recovery backstops only;
they are NOT a substitute for outputting the full review in your final response.
Write these artifacts to the working directory root:
- `review-findings.md` — Recovery backstop: incremental findings appended after
each phase, overwritten with the final synthesized review at the end. Used
only if the turn limit interrupts the run.
Write all review artifacts to the working directory root:
- `review-findings.md` — Incremental findings (appended after each phase),
then replaced with the final synthesized review at the end
- `context.md` — Codebase context (call chains, invariants)
- `findings-*.md` — Per-agent findings (design, correctness, cross-component,
invariant-adversary, caller-audit, performance, api, serialization, tests)
+104
View File
@@ -0,0 +1,104 @@
# Code Review Skill
This document defines the methodology for performing thorough, multi-perspective
code reviews on RocksDB changes. It is used both by CI (GitHub Actions) and
local review workflows.
## Prerequisites
Before starting a review:
- Read `CLAUDE.md` in the repository root for project-specific guidelines
- Read any files in `claude_md/` for architecture context
- Identify the commit/diff/PR to review and parse the changed files
## Review Methodology
Conduct the review from multiple perspectives, then synthesize findings into a
single report. Each perspective catches different classes of bugs.
### Perspective 1: Codebase Context & Call-Chain Analysis
Before reviewing the diff itself, build deep context:
- For each changed function/method, trace the call chain UPWARD 3-5 levels.
Who consumes the changed behavior? What invariants do callers rely on?
- Trace DOWNWARD into callees. What side effects do they have? What shared
state do they mutate (counters, sequence numbers, flags, metadata)?
- Identify the "sibling" or "reference" implementation that handles the same
scenario the standard way. Compare behaviors.
- For data written to shared structures (memtable, SST block, cache entry),
find ALL readers and verify their visibility rules match the writer.
### Perspective 2: Correctness & Edge Cases
- Thread safety and concurrency (lock ordering, data races)
- Error handling and Status propagation
- Edge cases: empty inputs, overflow, boundary conditions
- Data corruption scenarios
- Behavioral contract changes: do return value semantics match callers?
### Perspective 3: Cross-Component & Adversarial Analysis
Check the change in ALL execution contexts:
| Context | Key difference |
|---------|---------------|
| WritePreparedTxnDB | read_callback_ controls visibility |
| ReadOnly DB / SecondaryInstance | No mutable memtable |
| CompactionService / Remote compaction | Different process |
| User-defined timestamps | Extra key comparison dimension |
| MemPurge | Memtable-to-memtable path |
| BlobDB | Values in blob files, not inline |
| Old snapshots | Seqno far behind current |
| Concurrent writers | Lock-free vs locked paths |
| FIFO / Universal compaction | Different invariants |
| Prefix seek / total order seek | Different iterator behavior |
When the change claims a property ("logically redundant", "safe because X"),
systematically try to break it: list preconditions, construct counterexamples.
### Perspective 4: Performance
RocksDB is a high-performance storage engine.
- Memory allocation on hot paths
- Unnecessary copies (prefer move semantics, Slice, string_view)
- Cache efficiency and data locality
- Loop optimization, branch prediction (LIKELY/UNLIKELY)
### Perspective 5: API, Compatibility & Testing
- Public API backwards compatibility
- Serialization format correctness and versioning
- Test coverage for edge cases, failure modes, and system-level interactions
## How to Explore the Codebase
Use available tools to explore deeply — do NOT just review the diff in isolation.
The most critical bugs hide at component boundaries.
- Read the header files for changed implementations
- Search for callers of changed functions (3-5 levels up)
- Read existing tests to understand guaranteed behaviors
- Check related implementations for conventions and patterns
## Output Format
### Summary
Brief overall assessment (1-2 sentences).
### Issues Found
Categorize by severity:
- :red_circle: **Critical**: Must fix (correctness, data corruption, security)
- :yellow_circle: **Suggestion**: Should consider (performance, edge cases)
- :green_circle: **Nitpick**: Minor style issues
For each issue include:
- File and line reference
- Which review perspective found it
- Description with root cause analysis
- Suggested fix
### Cross-Component Analysis
Execution context checks and assumption stress-test results.
### Positive Observations
Good patterns, clever optimizations, or improvements.
-67
View File
@@ -1,67 +0,0 @@
# Manual (Local) Code Review — Lightweight Wrapper
## What this is
A resource-light, **single-reviewer** variant of `claude_md/ci_review_prompt.md`
for running a review by hand in a local Claude Code / Devmate session. It reuses
the full CI review *methodology* but drops the multi-agent orchestration and the
CI-only file-persistence machinery, to approximate the heavyweight result at a
fraction of the cost.
**Scope: LOCAL manual review only. This file is NOT used by CI.** CI uses
`claude_md/ci_review_prompt.md`.
## How to use
Read `claude_md/ci_review_prompt.md` for the complete methodology — the analysis
techniques (caller-chain, callee side-effects, cross-component data consumers,
alternative execution contexts, assumption stress-testing), the per-perspective
reviewer instructions, the review checklist, and the anti-patterns table.
Then apply that methodology with the OVERRIDES below. **Where this file
conflicts with `ci_review_prompt.md`, this file wins.**
## Overrides (this is what makes it lightweight)
### 1. Single reviewer — no team, no subagents
- Do NOT create a team, spawn subagents, or run parallel/background agents.
- Do NOT run the round-robin Debate phase or the Consensus phase.
- Instead, YOU personally apply each reviewer role from `ci_review_prompt.md`
(Design, Correctness, Cross-Component & Adversarial, Invariant Adversary,
Caller-Context, Performance, API/Compat, Serialization, Test Coverage) as
sequential *lenses* in one pass. Spend a few focused minutes per lens. Skip
lenses that plainly don't apply (e.g., Serialization when there is no on-disk
or wire-format change) and note the skip in one line.
### 2. No intermediate files
- Do NOT write `context.md`, `findings-*.md`, `consensus.md`, or
`review-findings.md`. Keep working notes in your reasoning, not on disk.
- Rationale: those files exist only so CI can recover partial results when an
ephemeral runner hits the turn limit — a local session has neither problem.
`ci_review_prompt.md` already outputs the review as its final response, so
skipping the file loses nothing here.
### 3. Right-size the effort
- Do the Codebase Context phase, but proportionally: trace the 2-3 most
load-bearing call chains and the highest-risk cross-component data consumers,
rather than exhaustively enumerating every caller 3-5 levels up.
- Prioritize the highest-signal lenses for RocksDB: **Correctness**,
**Cross-Component & Adversarial**, and **Assumption Stress-Testing**. These
catch the bugs that matter most; give them the most attention.
- Escalation clause: if a thread looks dangerous, go deep on that specific
thread even though the overall pass is modest. Modest is the default, not a cap.
### 4. Identify the review target locally
- Unlike CI, the diff is NOT pre-pasted into this prompt. Determine what to
review from the user's request against the local worktree — e.g. the current
commit (`git show`), uncommitted changes (`git diff` / `git diff --staged`),
a named commit range, or a PR — and read the changed files plus their
surrounding context directly from the repo. When in doubt, include all changes
not yet upstream, as in
`git diff "$(git merge-base upstream/main HEAD 2> /dev/null || git merge-base origin/main HEAD 2> /dev/null)"`
plus any untracked files (`git status`) that seem relevant.
## Reminder
The goal is a thorough *single* reviewer, not a 9-agent debate: depth where it
matters, brevity everywhere else.
+4
View File
@@ -44,6 +44,10 @@ if(@WITH_NUMA@)
find_dependency(NUMA)
endif()
if(@WITH_TBB@)
find_dependency(TBB)
endif()
find_dependency(Threads)
include("${CMAKE_CURRENT_LIST_DIR}/RocksDBTargets.cmake")
+33
View File
@@ -0,0 +1,33 @@
# - Find TBB
# Find the Thread Building Blocks library and includes
#
# TBB_INCLUDE_DIRS - where to find tbb.h, etc.
# TBB_LIBRARIES - List of libraries when using TBB.
# TBB_FOUND - True if TBB found.
if(NOT DEFINED TBB_ROOT_DIR)
set(TBB_ROOT_DIR "$ENV{TBBROOT}")
endif()
find_path(TBB_INCLUDE_DIRS
NAMES tbb/tbb.h
HINTS ${TBB_ROOT_DIR}/include)
find_library(TBB_LIBRARIES
NAMES tbb
HINTS ${TBB_ROOT_DIR}/lib ENV LIBRARY_PATH)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(TBB DEFAULT_MSG TBB_LIBRARIES TBB_INCLUDE_DIRS)
mark_as_advanced(
TBB_LIBRARIES
TBB_INCLUDE_DIRS)
if(TBB_FOUND AND NOT (TARGET TBB::TBB))
add_library (TBB::TBB UNKNOWN IMPORTED)
set_target_properties(TBB::TBB
PROPERTIES
IMPORTED_LOCATION ${TBB_LIBRARIES}
INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS})
endif()
-4
View File
@@ -25,7 +25,6 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
crash_test_with_multiops_wup_txn \
crash_test_with_optimistic_txn \
crash_test_with_tiered_storage \
liveness_crash_test \
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
blackbox_crash_test_with_wc_txn blackbox_crash_test_with_wp_txn \
blackbox_crash_test_with_wup_txn \
@@ -95,9 +94,6 @@ crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wup_txn
liveness_crash_test: $(DB_STRESS_CMD)
$(CRASHTEST_PY) liveness $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --simple blackbox $(CRASH_TEST_EXT_ARGS)
$(CRASHTEST_PY) blackbox $(CRASH_TEST_EXT_ARGS)
+26 -132
View File
@@ -30,78 +30,13 @@ inline static SequenceNumber GetSeqNum(const DBImpl* db, const Snapshot* s) {
Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
std::string* prop) {
if (prop_name == "rocksdb.iterator.super-version-number") {
if (!internal_iter_initialized_) {
*prop = std::to_string(sv_number_);
return Status::OK();
}
// First try to pass the value returned from inner iterator.
if (!db_iter_->GetProperty(prop_name, prop).ok()) {
*prop = std::to_string(sv_number_);
}
return Status::OK();
}
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return db_iter_->status();
}
return db_iter_->GetProperty(std::move(prop_name), prop);
}
void ArenaWrappedDBIter::CleanupDeferredSuperVersion() {
if (deferred_sv_ != nullptr) {
assert(db_impl_ != nullptr);
db_impl_->CleanupIteratorSuperVersion(
deferred_sv_, read_options_.background_purge_on_iterator_cleanup);
deferred_sv_ = nullptr;
}
}
void ArenaWrappedDBIter::DestroyDBIter() {
db_iter_->~DBIter();
CleanupDeferredSuperVersion();
}
void ArenaWrappedDBIter::ColumnFamilyDataUnrefDeleter::operator()(
ColumnFamilyData* cfd) const {
if (cfd == nullptr) {
return;
}
assert(db_impl != nullptr);
InstrumentedMutexLock lock(db_impl->mutex());
cfd->UnrefAndTryDelete();
}
void ArenaWrappedDBIter::DestroyDBIterAndArena() {
DestroyDBIter();
arena_.~Arena();
}
Status ArenaWrappedDBIter::EnsureInternalIteratorInitialized(
const MultiScanArgs* scan_opts) {
if (internal_iter_initialized_) {
return Status::OK();
}
if (db_impl_ == nullptr || deferred_cfd_ == nullptr ||
deferred_sv_ == nullptr) {
Status s = Status::InvalidArgument(
"Internal iterator cannot be initialized without deferred DB state");
db_iter_->set_status(s);
db_iter_->set_valid(false);
return s;
}
const MultiScanArgs* pruning_scan_opts =
scan_opts != nullptr && scan_opts->HasBoundedScanRanges() ? scan_opts
: nullptr;
child_read_options_ = read_options_;
child_read_options_.snapshot = nullptr;
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
child_read_options_, deferred_cfd_, deferred_sv_, &arena_, sequence_,
/*allow_unprepared_value=*/true, this, pruning_scan_opts);
deferred_cfd_ = nullptr;
deferred_sv_ = nullptr;
SetIterUnderDBIterImpl(internal_iter);
return Status::OK();
return db_iter_->GetProperty(prop_name, prop);
}
void ArenaWrappedDBIter::Init(
@@ -109,26 +44,18 @@ void ArenaWrappedDBIter::Init(
const MutableCFOptions& mutable_cf_options, const Version* version,
const SequenceNumber& sequence, uint64_t version_number,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, bool allow_refresh, ReadOnlyMemTable* active_mem,
DBImpl* db_impl, ColumnFamilyData* cfd) {
bool expose_blob_index, bool allow_refresh, ReadOnlyMemTable* active_mem) {
read_options_ = read_options;
child_read_options_ = read_options;
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
FSSupportedOps::kAsyncIO)) {
read_options_.async_io = false;
}
read_options_.total_order_seek |= ioptions.prefix_seek_opt_in_only;
if (cfh != nullptr) {
db_impl = cfh->db();
cfd = cfh->cfd();
}
db_iter_ = DBIter::NewIter(env, read_options_, ioptions, mutable_cf_options,
ioptions.user_comparator,
/*internal_iter=*/nullptr, version, sequence,
read_callback, active_mem, /*cfh=*/nullptr,
expose_blob_index, &arena_, db_impl, cfd);
db_iter_ = DBIter::NewIter(
env, read_options_, ioptions, mutable_cf_options,
ioptions.user_comparator, /*internal_iter=*/nullptr, version, sequence,
read_callback, active_mem, cfh, expose_blob_index, &arena_);
sv_number_ = version_number;
allow_refresh_ = allow_refresh;
@@ -138,13 +65,13 @@ void ArenaWrappedDBIter::Init(
void ArenaWrappedDBIter::MaybeAutoRefresh(bool is_seek,
DBIter::Direction direction) {
if (cfd_ref_ != nullptr && read_options_.snapshot != nullptr &&
allow_refresh_ && read_options_.auto_refresh_iterator_with_snapshot) {
if (cfh_ != nullptr && read_options_.snapshot != nullptr && allow_refresh_ &&
read_options_.auto_refresh_iterator_with_snapshot) {
// The intent here is to capture the superversion number change
// reasonably soon from the time it actually happened. As such,
// we're fine with weaker synchronization / ordering guarantees
// provided by relaxed atomic (in favor of less CPU / mem overhead).
uint64_t cur_sv_number = cfd_ref_->GetSuperVersionNumberRelaxed();
uint64_t cur_sv_number = cfh_->cfd()->GetSuperVersionNumberRelaxed();
if ((sv_number_ != cur_sv_number) && status().ok()) {
// Changing iterators' direction is pretty heavy-weight operation and
// could have unintended consequences when it comes to prefix seek.
@@ -223,13 +150,13 @@ void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
// present in the error log, but won't be reflected in the iterator status.
// This is by design as we expect compaction to clean up those obsolete files
// eventually.
DestroyDBIterAndArena();
db_iter_->~DBIter();
arena_.~Arena();
new (&arena_) Arena();
auto cfd = cfd_ref_.get();
auto db_impl = db_impl_;
assert(cfd != nullptr);
assert(db_impl != nullptr);
auto cfd = cfh_->cfd();
auto db_impl = cfh_->db();
SuperVersion* sv = cfd->GetReferencedSuperVersion(db_impl);
assert(sv->version_number >= sv_number);
@@ -237,27 +164,23 @@ void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
if (read_callback_) {
read_callback_->Refresh(read_seq);
}
// TODO: Preserve Prepare() scan options across Refresh() so a refreshed
// MultiScan iterator can rebuild the same pruned tree.
Init(env, read_options_, cfd->ioptions(), sv->mutable_cf_options, sv->current,
read_seq, sv->version_number, read_callback_, nullptr,
expose_blob_index_, allow_refresh_,
allow_mark_memtable_for_flush_ ? sv->mem : nullptr, db_impl, cfd);
read_seq, sv->version_number, read_callback_, cfh_, expose_blob_index_,
allow_refresh_, allow_mark_memtable_for_flush_ ? sv->mem : nullptr);
InternalIterator* internal_iter = db_impl->NewInternalIterator(
read_options_, cfd, sv, &arena_, read_seq,
/* allow_unprepared_value */ true, /* db_iter */ this);
SetIterUnderDBIter(internal_iter);
internal_iter_initialized_ = true;
}
Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
if (cfd_ref_ == nullptr || db_impl_ == nullptr || !allow_refresh_) {
if (cfh_ == nullptr || !allow_refresh_) {
return Status::NotSupported("Creating renew iterator is not allowed.");
}
assert(db_iter_ != nullptr);
auto cfd = cfd_ref_.get();
auto db_impl = db_impl_;
auto cfd = cfh_->cfd();
auto db_impl = cfh_->db();
// TODO(yiwu): For last_seq_same_as_publish_seq_==false, this is not the
// correct behavior. Will be corrected automatically when we take a snapshot
@@ -270,13 +193,6 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:1");
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:2");
if (!internal_iter_initialized_) {
Status s = EnsureInternalIteratorInitialized(nullptr);
if (!s.ok()) {
return s;
}
}
while (true) {
if (sv_number_ != cur_sv_number) {
DoRefresh(snapshot, cur_sv_number);
@@ -334,47 +250,25 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
return Status::OK();
}
void ArenaWrappedDBIter::Prepare(const MultiScanArgs& scan_opts) {
if (prepare_called_) {
db_iter_->set_status(Status::InvalidArgument(
"Prepare called more than once on the same iterator"));
db_iter_->set_valid(false);
return;
}
prepare_called_ = true;
Status s = db_iter_->SetScanOptionsForPrepare(scan_opts);
if (!s.ok()) {
return;
}
const MultiScanArgs* pruning_scan_opts =
scan_opts.HasBoundedScanRanges() ? &scan_opts : nullptr;
s = EnsureInternalIteratorInitialized(pruning_scan_opts);
if (!s.ok()) {
return;
}
db_iter_->PrepareInternalChildren();
}
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
Env* env, const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
SuperVersion* sv, const SequenceNumber& sequence,
ReadCallback* read_callback, DBImpl* db_impl, bool expose_blob_index,
bool allow_refresh, bool allow_mark_memtable_for_flush) {
ArenaWrappedDBIter* db_iter = new ArenaWrappedDBIter();
ColumnFamilyData* cfd = cfh->cfd();
db_iter->Init(env, read_options, cfh->cfd()->ioptions(),
sv->mutable_cf_options, sv->current, sequence,
sv->version_number, read_callback, cfh, expose_blob_index,
allow_refresh,
allow_mark_memtable_for_flush ? sv->mem : nullptr);
if (allow_refresh && cfd != nullptr) {
db_iter->StoreRefreshInfo(read_callback, expose_blob_index);
if (cfh != nullptr && allow_refresh) {
db_iter->StoreRefreshInfo(cfh, read_callback, expose_blob_index);
}
db_iter->StoreDeferredInitInfo(db_impl, cfd, sv, sequence,
allow_mark_memtable_for_flush);
InternalIterator* internal_iter = db_impl->NewInternalIterator(
db_iter->GetReadOptions(), cfh->cfd(), sv, db_iter->GetArena(), sequence,
/*allow_unprepared_value=*/true, db_iter);
db_iter->SetIterUnderDBIter(internal_iter);
return db_iter;
}
+17 -74
View File
@@ -10,7 +10,6 @@
#pragma once
#include <stdint.h>
#include <memory>
#include <string>
#include "db/db_impl/db_impl.h"
@@ -34,17 +33,10 @@ class Version;
// When using the class's Iterator interface, the behavior is exactly
// the same as the inner DBIter.
class ArenaWrappedDBIter : public Iterator {
struct ColumnFamilyDataUnrefDeleter {
DBImpl* db_impl = nullptr;
void operator()(ColumnFamilyData* cfd) const;
};
using ColumnFamilyDataRef =
std::unique_ptr<ColumnFamilyData, ColumnFamilyDataUnrefDeleter>;
public:
~ArenaWrappedDBIter() override {
if (db_iter_ != nullptr) {
DestroyDBIter();
db_iter_->~DBIter();
} else {
assert(false);
}
@@ -59,7 +51,7 @@ class ArenaWrappedDBIter : public Iterator {
// Set the internal iterator wrapped inside the DB Iterator. Usually it is
// a merging iterator.
virtual void SetIterUnderDBIter(InternalIterator* iter) {
SetIterUnderDBIterImpl(iter);
db_iter_->SetIter(iter);
}
void SetMemtableRangetombstoneIter(
@@ -68,48 +60,26 @@ class ArenaWrappedDBIter : public Iterator {
}
bool Valid() const override { return db_iter_->Valid(); }
void SeekToFirst() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->SeekToFirst();
}
void SeekToLast() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->SeekToLast();
}
void SeekToFirst() override { db_iter_->SeekToFirst(); }
void SeekToLast() override { db_iter_->SeekToLast(); }
// 'target' does not contain timestamp, even if user timestamp feature is
// enabled.
void Seek(const Slice& target) override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
MaybeAutoRefresh(true /* is_seek */, DBIter::kForward);
db_iter_->Seek(target);
}
void SeekForPrev(const Slice& target) override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
MaybeAutoRefresh(true /* is_seek */, DBIter::kReverse);
db_iter_->SeekForPrev(target);
}
void Next() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->Next();
MaybeAutoRefresh(false /* is_seek */, DBIter::kForward);
}
void Prev() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->Prev();
MaybeAutoRefresh(false /* is_seek */, DBIter::kReverse);
}
@@ -126,12 +96,11 @@ class ArenaWrappedDBIter : public Iterator {
Status Refresh() override;
Status Refresh(const Snapshot*) override;
bool PrepareValue() override {
return EnsureInternalIteratorInitialized(nullptr).ok() &&
db_iter_->PrepareValue();
}
bool PrepareValue() override { return db_iter_->PrepareValue(); }
void Prepare(const MultiScanArgs& scan_opts) override;
void Prepare(const MultiScanArgs& scan_opts) override {
db_iter_->Prepare(scan_opts);
}
// FIXME: we could just pass SV in for mutable cf option, version and version
// number, but this is used by SstFileReader which does not have a SV.
@@ -141,53 +110,27 @@ class ArenaWrappedDBIter : public Iterator {
const SequenceNumber& sequence, uint64_t version_number,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, bool allow_refresh,
ReadOnlyMemTable* active_mem, DBImpl* db_impl = nullptr,
ColumnFamilyData* cfd = nullptr);
ReadOnlyMemTable* active_mem);
// Store parameters used only by explicit/auto-refresh.
void StoreRefreshInfo(ReadCallback* read_callback, bool expose_blob_index) {
// Store some parameters so we can refresh the iterator at a later point
// with these same params
void StoreRefreshInfo(ColumnFamilyHandleImpl* cfh,
ReadCallback* read_callback, bool expose_blob_index) {
cfh_ = cfh;
read_callback_ = read_callback;
expose_blob_index_ = expose_blob_index;
}
void StoreDeferredInitInfo(DBImpl* db_impl, ColumnFamilyData* cfd,
SuperVersion* sv, const SequenceNumber& sequence,
bool allow_mark_memtable_for_flush) {
assert(cfd != nullptr);
db_impl_ = db_impl;
cfd->Ref();
cfd_ref_ = ColumnFamilyDataRef(cfd, ColumnFamilyDataUnrefDeleter{db_impl});
deferred_cfd_ = cfd;
deferred_sv_ = sv;
sequence_ = sequence;
allow_mark_memtable_for_flush_ = allow_mark_memtable_for_flush;
}
private:
Status EnsureInternalIteratorInitialized(const MultiScanArgs* scan_opts);
void SetIterUnderDBIterImpl(InternalIterator* iter) {
db_iter_->SetIter(iter);
internal_iter_initialized_ = true;
}
void CleanupDeferredSuperVersion();
void DestroyDBIter();
void DestroyDBIterAndArena();
void DoRefresh(const Snapshot* snapshot, uint64_t sv_number);
void MaybeAutoRefresh(bool is_seek, DBIter::Direction direction);
DBIter* db_iter_ = nullptr;
Arena arena_;
uint64_t sv_number_ = 0;
DBImpl* db_impl_ = nullptr;
ColumnFamilyDataRef cfd_ref_{nullptr, ColumnFamilyDataUnrefDeleter{}};
ColumnFamilyData* deferred_cfd_ = nullptr;
SuperVersion* deferred_sv_ = nullptr;
SequenceNumber sequence_ = kMaxSequenceNumber;
bool internal_iter_initialized_ = false;
bool prepare_called_ = false;
uint64_t sv_number_;
ColumnFamilyHandleImpl* cfh_ = nullptr;
ReadOptions read_options_;
ReadOptions child_read_options_;
ReadCallback* read_callback_ = nullptr;
ReadCallback* read_callback_;
bool expose_blob_index_ = false;
bool allow_refresh_ = true;
bool allow_mark_memtable_for_flush_ = true;
-59
View File
@@ -5,71 +5,12 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits>
#include "rocksdb/rocksdb_namespace.h"
namespace ROCKSDB_NAMESPACE {
// WARNING: This value is == kCurrentFileBlobIndexFileNumber.
// Use this name only where file number zero means "no valid blob file" or
// "current file" is not understood/supported.
constexpr uint64_t kInvalidBlobFileNumber = 0;
// WARNING: This value is == kInvalidBlobFileNumber.
// Use this name only for BlobIndex references to the same physical file as what
// is currently being read; generic blob-file metadata must treat zero as
// invalid. (Using a distinct value like 1 was found to be more problematic,
// e.g. because of legacy "stackable" blob implementation.)
//
// This "zero is invalid unless you are the embedded reader/writer" contract is
// enforced by integrity checks that reject file number zero on generic paths;
// see FileMetaData::UpdateBoundaries (write/output path) and Version::GetBlob /
// Version::MultiGetBlob (read path). Same-file references must be resolved (by
// EmbeddedBlobResolvingIterator) before they reach those paths. Do not weaken
// those checks -- they catch leaks/corruption closer to the root cause.
constexpr uint64_t kCurrentFileBlobIndexFileNumber = kInvalidBlobFileNumber;
static_assert(kCurrentFileBlobIndexFileNumber == kInvalidBlobFileNumber);
// Sentinel range length meaning "the whole blob value" for the unified
// blob-read primitives, which take a range descriptor {range_offset,
// range_length}; range_length == kWholeBlobLength selects the whole value and
// any other value selects the strict sub-range
// [range_offset, range_offset + range_length).
//
// This is the internal counterpart of the public kLazyWholeColumn
// (include/rocksdb/lazy_wide_columns.h); both are the maximum size_t. The lazy
// resolver does not forward the public value as a range descriptor -- it
// normalizes a whole-column request to kWholeBlobLength -- so the two are not
// required to be equal, but both rely on the sentinel exceeding any real
// blob/column size so a "whole" request is never mistaken for a strict
// sub-range. Keep them in sync.
inline constexpr size_t kWholeBlobLength = std::numeric_limits<size_t>::max();
// How aggressively a blob read verifies the record's checksum. Derived once at
// the lazy read-path boundary (ReadPathBlobResolver) from
// (ReadOptions::verify_checksums, LazyColumnReadRequest::force_verify) and
// threaded down through the blob-read primitives, replacing the earlier mix of
// a verify_checksums bool and a separate force-verify method. (Not literally a
// constant, but kept here as a small shared blob-read enum rather than its own
// header.)
//
// - kVerifyIfPresent: verify the whole-record checksum whenever the format
// provides one, even if ReadOptions::verify_checksums is off. A byte-range
// request under this policy is escalated to a whole-record read (a strict
// sub-range cannot cover the checksum). This is the public force_verify.
// - kVerifyIfNoAmplification: verify when the checksummable unit is already
// being read -- a whole-value read verifies (the check is "free"), while a
// strict sub-range read skips verification rather than amplify the read to
// the whole record. Corresponds to verify_checksums on, force_verify off.
// - kSkip: never verify. Corresponds to verify_checksums off, force_verify
// off.
enum class BlobVerifyPolicy : uint8_t {
kVerifyIfPresent,
kVerifyIfNoAmplification,
kSkip,
};
} // namespace ROCKSDB_NAMESPACE
+7 -78
View File
@@ -7,7 +7,6 @@
#include "db/blob/blob_file_partition_manager.h"
#include "db/blob/blob_index.h"
#include "db/blob/same_file_blob_reader.h"
#include "db/version_set.h"
namespace ROCKSDB_NAMESPACE {
@@ -26,91 +25,21 @@ Status BlobFetcher::FetchBlob(const Slice& user_key,
return status;
}
Status VersionBlobFetcherBase::FetchBlob(const Slice& user_key,
const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const {
const ReadOptions& read_options = this->read_options();
Status BlobFetcher::FetchBlob(const Slice& user_key,
const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const {
if (!allow_write_path_fallback_) {
assert(version_);
return version_->GetBlob(read_options, user_key, blob_index,
return version_->GetBlob(read_options_, user_key, blob_index,
prefetch_buffer, blob_value, bytes_read);
}
return BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
read_options, user_key, blob_index, version_, blob_file_cache_,
read_options_, user_key, blob_index, version_, blob_file_cache_,
prefetch_buffer, blob_value, bytes_read);
}
Status VersionBlobFetcherBase::FetchBlobRange(
const Slice& user_key, const BlobIndex& blob_index, uint64_t range_offset,
size_t range_length, BlobVerifyPolicy verify_policy,
PinnableSlice* blob_value, uint64_t* bytes_read) const {
if (range_length == kWholeBlobLength) {
// Whole-value read. Delegate to FetchBlob (which handles the direct-write
// fallback). For kVerifyIfPresent, force whole-record checksum verification
// even when ReadOptions::verify_checksums is off, via a transient fetcher
// over a verify-enabled copy of the read options (both locals outlive the
// borrowed-options VersionBlobFetcher).
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
if (verify_policy == BlobVerifyPolicy::kVerifyIfPresent &&
!read_options().verify_checksums) {
ReadOptions verify_read_options = read_options();
verify_read_options.verify_checksums = true;
VersionBlobFetcher verifying_fetcher(version_, verify_read_options,
blob_file_cache_,
allow_write_path_fallback_);
return verifying_fetcher.FetchBlob(user_key, blob_index, prefetch_buffer,
blob_value, bytes_read);
}
return FetchBlob(user_key, blob_index, prefetch_buffer, blob_value,
bytes_read);
}
// Strict sub-range read: value bytes only, no whole-record checksum, no cache
// fill. Only ever chosen when SupportsRangeRead() holds (a Version to read
// through, no direct-write fallback) and verification is not forced (a strict
// sub-range cannot cover the whole-record checksum).
assert(SupportsRangeRead());
assert(version_);
assert(verify_policy != BlobVerifyPolicy::kVerifyIfPresent);
return version_->GetBlobRange(read_options(), user_key, blob_index,
range_offset, range_length, blob_value,
bytes_read);
}
Status EmbeddedAwareBlobFetcher::FetchBlob(const Slice& user_key,
const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const {
// Only an enabled decorator is ever routed to (see EffectiveFetcher()).
assert(same_file_reader_ != nullptr);
if (blob_index.IsSameFile()) {
// Same-file ("embedded") blob records live in the current SST and are read
// by the SameFileBlobReader (the BlockBasedTable). A separate blob-file
// prefetch buffer and bytes_read counter do not apply here (embedded reads
// account their own BLOB_DB_* stats via BlobSource). This is a whole-value
// read; the verify policy follows read_options().verify_checksums (the
// decorator carries no separate force-verify signal).
const BlobVerifyPolicy verify_policy =
read_options().verify_checksums
? BlobVerifyPolicy::kVerifyIfNoAmplification
: BlobVerifyPolicy::kSkip;
return same_file_reader_->GetSameFileBlob(
read_options(), blob_index,
/*range_offset=*/0, kWholeBlobLength, verify_policy, blob_value);
}
if (base_ == nullptr) {
return Status::Corruption(
"Cannot resolve non-same-file blob reference without a base blob "
"fetcher");
}
return base_->FetchBlob(user_key, blob_index, prefetch_buffer, blob_value,
bytes_read);
}
} // namespace ROCKSDB_NAMESPACE
+13 -191
View File
@@ -5,9 +5,6 @@
#pragma once
#include <utility>
#include "db/blob/blob_constants.h"
#include "rocksdb/options.h"
#include "rocksdb/status.h"
@@ -19,207 +16,32 @@ class Slice;
class FilePrefetchBuffer;
class PinnableSlice;
class BlobIndex;
class SameFileBlobReader;
// Abstract interface for blob retrieval on the read path. An implementation
// resolves a BlobIndex to its value, pinning it into a PinnableSlice. Callers
// hold a plain const BlobFetcher* so resolution can be intercepted by a
// decorator (e.g. EmbeddedAwareBlobFetcher) without their knowledge.
// A thin wrapper around blob retrieval. By default it reads through Version,
// and it can optionally fall back to direct-write blob files that are not yet
// manifest-visible.
class BlobFetcher {
public:
virtual ~BlobFetcher() = default;
BlobFetcher(const Version* version, const ReadOptions& read_options,
BlobFileCache* blob_file_cache = nullptr,
bool allow_write_path_fallback = false)
: version_(version),
read_options_(read_options),
blob_file_cache_(blob_file_cache),
allow_write_path_fallback_(allow_write_path_fallback) {}
// Convenience overload: decodes `blob_index_slice` into a BlobIndex and
// dispatches to the virtual overload below. Non-virtual; shared by all
// implementations.
Status FetchBlob(const Slice& user_key, const Slice& blob_index_slice,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value, uint64_t* bytes_read) const;
virtual Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const = 0;
// The read options this fetcher operates under. Used (via an
// EmbeddedAwareBlobFetcher decorator) to resolve same-file blob references,
// which need the current read_tier.
virtual const ReadOptions& read_options() const = 0;
};
// Shared implementation of the two Version-backed BlobFetchers below. Reads
// through a Version, optionally falling back to direct-write blob files that
// are not yet manifest-visible. Everything except the ReadOptions is stored
// here; the concrete subclass decides whether it references a caller-owned
// ReadOptions (VersionBlobFetcher) or owns its own copy
// (OwningVersionBlobFetcher). FetchBlob reads the options via the virtual
// read_options() so both storage strategies share one implementation.
class VersionBlobFetcherBase : public BlobFetcher {
public:
// Un-hide the base's Slice-based convenience overload.
using BlobFetcher::FetchBlob;
Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const override;
// Fetches the value of a separate-file blob reference into *blob_value,
// applying `verify_policy` (see BlobVerifyPolicy). range_length ==
// kWholeBlobLength reads the whole value (delegating to FetchBlob, which also
// handles the direct-write fallback, and forcing whole-record checksum
// verification for kVerifyIfPresent even when ReadOptions::verify_checksums
// is off). Any other length reads only the sub-range [range_offset,
// range_offset + range_length) directly from the blob file, reading just
// those bytes on a cache miss -- no record-header/key read, no whole-record
// checksum, no cache fill (see Version::GetBlobRange /
// BlobSource::GetBlobRange). A strict sub-range is only valid when
// SupportsRangeRead() is true and verify_policy != kVerifyIfPresent
// (verifying a strict sub-range would have to amplify the read to the whole
// record); callers check that first and take the whole-value path otherwise.
// Not routed through the same-file (embedded) decorator -- same-file
// references are resolved by the caller through a SameFileBlobReader.
Status FetchBlobRange(const Slice& user_key, const BlobIndex& blob_index,
uint64_t range_offset, size_t range_length,
BlobVerifyPolicy verify_policy,
PinnableSlice* blob_value, uint64_t* bytes_read) const;
// True if this fetcher has enough context to resolve a (non-inlined,
// non-same-file) blob reference: either a Version to read through, or the
// direct-write fallback (a blob file cache) enabled. Callers that may hold a
// null Version (e.g. DBIter) can check this to surface a clean Corruption on
// an unexpected blob index instead of dereferencing null in FetchBlob.
bool CanResolve() const {
return version_ != nullptr ||
(allow_write_path_fallback_ && blob_file_cache_ != nullptr);
}
// True if this fetcher can serve an I/O-saving byte-range read: it reads
// through a Version (FetchBlobRange goes to Version::GetBlobRange) and is not
// in direct-write fallback mode (whose reads resolve whole records instead).
bool SupportsRangeRead() const {
return version_ != nullptr && !allow_write_path_fallback_;
}
// The Version this fetcher reads separate-file blob references through (may
// be null for a direct-write-fallback-only fetcher). Used by the cross-key
// lazy coalescing path to group and dispatch reads via
// Version::MultiGetBlobLazy.
const Version* version() const { return version_; }
protected:
VersionBlobFetcherBase(const Version* version, BlobFileCache* blob_file_cache,
bool allow_write_path_fallback)
: version_(version),
blob_file_cache_(blob_file_cache),
allow_write_path_fallback_(allow_write_path_fallback) {}
PinnableSlice* blob_value, uint64_t* bytes_read) const;
private:
const Version* version_;
ReadOptions read_options_;
BlobFileCache* blob_file_cache_;
bool allow_write_path_fallback_;
};
// The default BlobFetcher for transient/stack use: holds a reference to a
// caller-owned ReadOptions rather than a copy, avoiding a per-fetcher copy of
// the (growing) ReadOptions on the hot path. The referenced ReadOptions must
// outlive the fetcher, so this is only appropriate for fetchers whose lifetime
// is bounded by the caller's (e.g. a stack local per Get / per resolution).
// Use OwningVersionBlobFetcher for lifetime-independent fetchers.
class VersionBlobFetcher : public VersionBlobFetcherBase {
public:
VersionBlobFetcher(const Version* version, const ReadOptions& read_options,
BlobFileCache* blob_file_cache = nullptr,
bool allow_write_path_fallback = false)
: VersionBlobFetcherBase(version, blob_file_cache,
allow_write_path_fallback),
read_options_(read_options) {}
// Reject binding to a temporary ReadOptions, which would leave a dangling
// reference. Callers that cannot guarantee the ReadOptions outlives the
// fetcher must use OwningVersionBlobFetcher instead.
VersionBlobFetcher(const Version* version, ReadOptions&& read_options,
BlobFileCache* blob_file_cache = nullptr,
bool allow_write_path_fallback = false) = delete;
const ReadOptions& read_options() const override { return read_options_; }
private:
const ReadOptions& read_options_;
};
// A BlobFetcher that owns its ReadOptions, for fetchers whose lifetime is
// independent of the caller that created them (the lazy read-path resolver and
// compaction). Takes ownership of the ReadOptions by rvalue so ownership
// transfer is explicit and no hidden copy is made on the way in; later reads
// never touch caller state. Callers holding a borrowed ReadOptions they cannot
// move must make the copy explicit (e.g. ReadOptions(read_options)).
class OwningVersionBlobFetcher : public VersionBlobFetcherBase {
public:
OwningVersionBlobFetcher(const Version* version, ReadOptions&& read_options,
BlobFileCache* blob_file_cache = nullptr,
bool allow_write_path_fallback = false)
: VersionBlobFetcherBase(version, blob_file_cache,
allow_write_path_fallback),
read_options_(std::move(read_options)) {}
const ReadOptions& read_options() const override { return read_options_; }
private:
ReadOptions read_options_;
};
// A BlobFetcher decorator that resolves same-file ("embedded") blob references
// against a SameFileBlobReader (the current SST) and delegates all other
// references to a base BlobFetcher. It is cheap to construct as a stack local
// per resolution: the same-file target is per-SST while the base fetcher is one
// per-Get object shared across SSTs, so an immutable per-SST decorator avoids
// shared-mutable-state hazards under async MultiGet.
//
// A decorator is "enabled" when it has a same-file reader and "disabled"
// (unusable) when constructed with a null `same_file_reader` (e.g. via the
// default constructor). A disabled decorator must not be routed through
// directly; callers construct one unconditionally and use EffectiveFetcher(),
// which returns this decorator when enabled or the undecorated base fetcher
// otherwise. This avoids std::optional / branching at call sites while adding
// no indirection on the common (non-embedded) path.
//
// The base fetcher may be null for an entity whose blob columns are all
// same-file (no separate-file blob support wired, e.g. SstFileReader); a
// non-same-file reference then surfaces as a Corruption rather than silently.
class EmbeddedAwareBlobFetcher : public BlobFetcher {
public:
explicit EmbeddedAwareBlobFetcher(
const BlobFetcher* base = nullptr,
const SameFileBlobReader* same_file_reader = nullptr)
: base_(base), same_file_reader_(same_file_reader) {}
// The fetcher to route resolution through: this decorator when it has a
// same-file reader, otherwise the (undecorated) base fetcher. Only an enabled
// decorator's FetchBlob is ever invoked.
const BlobFetcher* EffectiveFetcher() const {
return same_file_reader_ != nullptr ? this : base_;
}
// Un-hide the base's Slice-based convenience overload.
using BlobFetcher::FetchBlob;
Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* blob_value,
uint64_t* bytes_read) const override;
const ReadOptions& read_options() const override {
// Same-file resolution uses the base fetcher's read options; when there is
// no base (e.g. SstFileReader) a default (kReadAllTier) suffices.
static const ReadOptions kDefaultReadOptions;
return base_ != nullptr ? base_->read_options() : kDefaultReadOptions;
}
private:
const BlobFetcher* base_;
// Null iff this decorator is disabled (see class comment); otherwise the
// same-file blob reader for the current SST.
const SameFileBlobReader* same_file_reader_;
};
} // namespace ROCKSDB_NAMESPACE
+19 -33
View File
@@ -22,7 +22,6 @@
#include "logging/logging.h"
#include "options/cf_options.h"
#include "options/options_helper.h"
#include "rocksdb/file_system.h"
#include "rocksdb/slice.h"
#include "rocksdb/status.h"
#include "test_util/sync_point.h"
@@ -41,14 +40,14 @@ BlobFileBuilder::BlobFileBuilder(
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason,
std::vector<std::string>* output_file_paths,
std::vector<std::string>* blob_file_paths,
std::vector<BlobFileAddition>* blob_file_additions)
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
immutable_options, mutable_cf_options, file_options,
write_options, db_id, db_session_id, job_id,
column_family_id, column_family_name, write_hint,
io_tracer, blob_callback, creation_reason,
output_file_paths, blob_file_additions) {}
blob_file_paths, blob_file_additions) {}
BlobFileBuilder::BlobFileBuilder(
std::function<uint64_t()> file_number_generator, FileSystem* fs,
@@ -60,15 +59,13 @@ BlobFileBuilder::BlobFileBuilder(
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason,
std::vector<std::string>* output_file_paths,
std::vector<std::string>* blob_file_paths,
std::vector<BlobFileAddition>* blob_file_additions)
: file_number_generator_(std::move(file_number_generator)),
fs_(fs),
immutable_options_(immutable_options),
min_blob_size_(mutable_cf_options->min_blob_size),
blob_file_size_(mutable_cf_options->blob_file_size),
blob_file_writable_file_max_buffer_size_(
mutable_cf_options->blob_file_writable_file_max_buffer_size),
blob_compression_type_(mutable_cf_options->blob_compression_type),
// TODO with schema change: support custom compression manager and options
// such as max_compressed_bytes_per_kb
@@ -90,7 +87,7 @@ BlobFileBuilder::BlobFileBuilder(
io_tracer_(io_tracer),
blob_callback_(blob_callback),
creation_reason_(creation_reason),
output_file_paths_(output_file_paths),
blob_file_paths_(blob_file_paths),
blob_file_additions_(blob_file_additions),
blob_count_(0),
blob_bytes_(0) {
@@ -99,21 +96,14 @@ BlobFileBuilder::BlobFileBuilder(
assert(immutable_options_);
assert(file_options_);
assert(write_options_);
assert(output_file_paths_);
assert(output_file_paths_->empty());
assert(blob_file_paths_);
assert(blob_file_paths_->empty());
assert(blob_file_additions_);
assert(blob_file_additions_->empty());
}
BlobFileBuilder::~BlobFileBuilder() = default;
std::string BlobFileBuilder::GetBlobFilePath(uint64_t blob_file_number) const {
assert(immutable_options_);
assert(!immutable_options_->cf_paths.empty());
return BlobFileName(immutable_options_->cf_paths.front().path,
blob_file_number);
}
Status BlobFileBuilder::Add(const Slice& key, const Slice& value,
std::string* blob_index) {
assert(blob_index);
@@ -195,7 +185,10 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
assert(file_number_generator_);
const uint64_t blob_file_number = file_number_generator_();
std::string blob_file_path = GetBlobFilePath(blob_file_number);
assert(immutable_options_);
assert(!immutable_options_->cf_paths.empty());
std::string blob_file_path =
BlobFileName(immutable_options_->cf_paths.front().path, blob_file_number);
if (blob_callback_) {
blob_callback_->OnBlobFileCreationStarted(
@@ -207,13 +200,7 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
{
assert(file_options_);
fo_copy = *file_options_;
fo_copy.open_contract = FileOpenContract::kNoReopenForWrite |
FileOpenContract::kNoReadersWhileOpenForWrite;
fo_copy.write_hint = write_hint_;
if (blob_file_writable_file_max_buffer_size_ > 0) {
fo_copy.writable_file_max_buffer_size =
blob_file_writable_file_max_buffer_size_;
}
Status s = NewWritableFile(fs_, blob_file_path, &file, fo_copy);
TEST_SYNC_POINT_CALLBACK(
@@ -224,11 +211,11 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
}
}
// Note: blob files get added to output_file_paths_ right after the open, so
// they can be cleaned up upon failure. Contrast this with
// blob_file_additions_, which only contains successfully written files.
assert(output_file_paths_);
output_file_paths_->emplace_back(std::move(blob_file_path));
// Note: files get added to blob_file_paths_ right after the open, so they
// can be cleaned up upon failure. Contrast this with blob_file_additions_,
// which only contains successfully written files.
assert(blob_file_paths_);
blob_file_paths_->emplace_back(std::move(blob_file_path));
assert(file);
file->SetIOPriority(write_options_->rate_limiter_priority);
@@ -238,7 +225,7 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types;
Statistics* const statistics = immutable_options_->stats;
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
std::move(file), output_file_paths_->back(), fo_copy,
std::move(file), blob_file_paths_->back(), *file_options_,
immutable_options_->clock, io_tracer_, statistics,
Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS, immutable_options_->listeners,
immutable_options_->file_checksum_gen_factory.get(),
@@ -351,7 +338,7 @@ Status BlobFileBuilder::CloseBlobFile() {
if (blob_callback_) {
s = blob_callback_->OnBlobFileCompleted(
GetBlobFilePath(blob_file_number), column_family_name_, job_id_,
blob_file_paths_->back(), column_family_name_, job_id_,
blob_file_number, creation_reason_, s, checksum_value, checksum_method,
blob_count_, blob_bytes_);
}
@@ -395,10 +382,9 @@ void BlobFileBuilder::Abandon(const Status& s) {
if (blob_callback_) {
// BlobFileBuilder::Abandon() is called because of error while writing to
// Blob files. So we can ignore the below error.
const uint64_t blob_file_number = writer_->get_log_number();
blob_callback_
->OnBlobFileCompleted(GetBlobFilePath(blob_file_number),
column_family_name_, job_id_, blob_file_number,
->OnBlobFileCompleted(blob_file_paths_->back(), column_family_name_,
job_id_, writer_->get_log_number(),
creation_reason_, s, "", "", blob_count_,
blob_bytes_)
.PermitUncheckedError();
+3 -5
View File
@@ -48,7 +48,7 @@ class BlobFileBuilder {
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason,
std::vector<std::string>* output_file_paths,
std::vector<std::string>* blob_file_paths,
std::vector<BlobFileAddition>* blob_file_additions);
BlobFileBuilder(std::function<uint64_t()> file_number_generator,
@@ -63,7 +63,7 @@ class BlobFileBuilder {
const std::shared_ptr<IOTracer>& io_tracer,
BlobFileCompletionCallback* blob_callback,
BlobFileCreationReason creation_reason,
std::vector<std::string>* output_file_paths,
std::vector<std::string>* blob_file_paths,
std::vector<BlobFileAddition>* blob_file_additions);
BlobFileBuilder(const BlobFileBuilder&) = delete;
@@ -76,7 +76,6 @@ class BlobFileBuilder {
void Abandon(const Status& s);
private:
std::string GetBlobFilePath(uint64_t blob_file_number) const;
bool IsBlobFileOpen() const;
Status OpenBlobFileIfNeeded();
Status CompressBlobIfNeeded(Slice* blob,
@@ -94,7 +93,6 @@ class BlobFileBuilder {
const ImmutableOptions* immutable_options_;
uint64_t min_blob_size_;
uint64_t blob_file_size_;
uint64_t blob_file_writable_file_max_buffer_size_;
CompressionType blob_compression_type_;
std::unique_ptr<Compressor> blob_compressor_;
mutable Compressor::ManagedWorkingArea blob_compressor_wa_;
@@ -110,7 +108,7 @@ class BlobFileBuilder {
std::shared_ptr<IOTracer> io_tracer_;
BlobFileCompletionCallback* blob_callback_;
BlobFileCreationReason creation_reason_;
std::vector<std::string>* output_file_paths_;
std::vector<std::string>* blob_file_paths_;
std::vector<BlobFileAddition>* blob_file_additions_;
std::unique_ptr<BlobLogWriter> writer_;
uint64_t blob_count_;
-1
View File
@@ -123,7 +123,6 @@ Status BlobFileCache::OpenBlobFileReaderUncached(
Statistics* const statistics = immutable_options_->stats;
RecordTick(statistics, NO_FILE_OPENS);
assert(file_options_);
Status s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
+3 -13
View File
@@ -24,7 +24,6 @@
#include "file/read_write_util.h"
#include "file/writable_file_writer.h"
#include "logging/logging.h"
#include "rocksdb/file_system.h"
#include "util/aligned_buffer.h"
#include "util/compression.h"
#include "util/mutexlock.h"
@@ -100,8 +99,7 @@ BlobFilePartitionManager::BlobFilePartitionManager(
FileNumberAllocator file_number_allocator, FileSystem* fs,
SystemClock* clock, Statistics* statistics, const FileOptions& file_options,
std::string db_path, std::string column_family_name,
uint64_t blob_file_size, bool use_fsync,
uint64_t blob_writer_max_buffer_size, BlobFileCache* blob_file_cache,
uint64_t blob_file_size, bool use_fsync, BlobFileCache* blob_file_cache,
BlobFileCompletionCallback* blob_callback,
const std::vector<std::shared_ptr<EventListener>>& listeners,
FileChecksumGenFactory* file_checksum_gen_factory,
@@ -121,7 +119,6 @@ BlobFilePartitionManager::BlobFilePartitionManager(
column_family_name_(std::move(column_family_name)),
blob_file_size_(blob_file_size),
use_fsync_(use_fsync),
blob_writer_max_buffer_size_(blob_writer_max_buffer_size),
blob_file_cache_(blob_file_cache),
blob_callback_(blob_callback),
listeners_(listeners),
@@ -202,14 +199,7 @@ Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
}
std::unique_ptr<FSWritableFile> file;
FileOptions writer_file_options = file_options_;
writer_file_options.open_contract = FileOpenContract::kNoReopenForWrite;
const uint64_t max_buffer_size =
blob_writer_max_buffer_size_.load(std::memory_order_relaxed);
if (max_buffer_size > 0) {
writer_file_options.writable_file_max_buffer_size = max_buffer_size;
}
Status s = NewWritableFile(fs_, blob_file_path, &file, writer_file_options);
Status s = NewWritableFile(fs_, blob_file_path, &file, file_options_);
if (!s.ok()) {
RemoveFilePartitionMapping(blob_file_number);
return s;
@@ -218,7 +208,7 @@ Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
const bool perform_data_verification =
checksum_handoff_file_types_.Contains(FileType::kBlobFile);
auto file_writer = std::make_unique<WritableFileWriter>(
std::move(file), blob_file_path, writer_file_options, clock_, io_tracer_,
std::move(file), blob_file_path, file_options_, clock_, io_tracer_,
statistics_, Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS, listeners_,
file_checksum_gen_factory_, perform_data_verification);
+1 -9
View File
@@ -4,7 +4,6 @@
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include <atomic>
#include <cstdint>
#include <deque>
#include <functional>
@@ -62,8 +61,7 @@ class BlobFilePartitionManager {
SystemClock* clock, Statistics* statistics,
const FileOptions& file_options, std::string db_path,
std::string column_family_name, uint64_t blob_file_size, bool use_fsync,
uint64_t blob_writer_max_buffer_size, BlobFileCache* blob_file_cache,
BlobFileCompletionCallback* blob_callback,
BlobFileCache* blob_file_cache, BlobFileCompletionCallback* blob_callback,
const std::vector<std::shared_ptr<EventListener>>& listeners,
FileChecksumGenFactory* file_checksum_gen_factory,
const FileTypeSet& checksum_handoff_file_types,
@@ -131,11 +129,6 @@ class BlobFilePartitionManager {
// protected by a live memtable / old SuperVersion.
bool IsTrackedBlobFileNumber(uint64_t file_number) const;
void SetBlobWriterMaxBufferSize(uint64_t blob_writer_max_buffer_size) {
blob_writer_max_buffer_size_.store(blob_writer_max_buffer_size,
std::memory_order_relaxed);
}
// Increments / decrements memtable-held protection on sealed blob files.
void ProtectSealedBlobFileNumbers(const std::vector<uint64_t>& file_numbers);
void UnprotectSealedBlobFileNumbers(
@@ -267,7 +260,6 @@ class BlobFilePartitionManager {
// File creation policy and shared blob-file infrastructure.
uint64_t blob_file_size_;
bool use_fsync_;
std::atomic<uint64_t> blob_writer_max_buffer_size_;
BlobFileCache* blob_file_cache_;
BlobFileCompletionCallback* blob_callback_;
std::vector<std::shared_ptr<EventListener>> listeners_;
+36 -306
View File
@@ -12,7 +12,6 @@
#include "db/blob/blob_log_format.h"
#include "file/file_prefetch_buffer.h"
#include "file/filename.h"
#include "file/read_write_util.h"
#include "monitoring/statistics_impl.h"
#include "options/cf_options.h"
#include "rocksdb/file_system.h"
@@ -21,7 +20,6 @@
#include "table/format.h"
#include "table/multiget_context.h"
#include "test_util/sync_point.h"
#include "util/aligned_buffer.h"
#include "util/compression.h"
#include "util/stop_watch.h"
@@ -107,6 +105,24 @@ Status BlobFileReader::OpenFile(
constexpr IODebugContext* dbg = nullptr;
{
TEST_SYNC_POINT("BlobFileReader::OpenFile:GetFileSize");
const Status s =
fs->GetFileSize(blob_file_path, IOOptions(), file_size, dbg);
if (!s.ok()) {
return s;
}
}
if (!skip_footer_size_check &&
*file_size < BlobLogHeader::kSize + BlobLogFooter::kSize) {
return Status::Corruption("Malformed blob file");
}
if (skip_footer_size_check && *file_size < BlobLogHeader::kSize) {
return Status::Corruption("Malformed blob file");
}
std::unique_ptr<FSRandomAccessFile> file;
FileOptions reader_file_opts = file_opts;
@@ -126,24 +142,6 @@ Status BlobFileReader::OpenFile(
assert(file);
{
Status s = GetFileSizeFromOpenFileOrPath(
file.get(), fs, blob_file_path, file_size, dbg,
FileSizeFallback::kNotSupportedOnly,
[]() { TEST_SYNC_POINT("BlobFileReader::OpenFile:GetFileSize"); });
if (!s.ok()) {
return s;
}
}
if (!skip_footer_size_check &&
*file_size < BlobLogHeader::kSize + BlobLogFooter::kSize) {
return Status::Corruption("Malformed blob file");
}
if (skip_footer_size_check && *file_size < BlobLogHeader::kSize) {
return Status::Corruption("Malformed blob file");
}
if (immutable_options.advise_random_on_open) {
file->Hint(FSRandomAccessFile::kRandom);
}
@@ -167,7 +165,7 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
Slice header_slice;
Buffer buf;
AlignedBuffer direct_io_buffer;
AlignedBuf aligned_buf;
{
TEST_SYNC_POINT("BlobFileReader::ReadHeader:ReadFromFile");
@@ -177,7 +175,7 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
const Status s =
ReadFromFile(file_reader, read_options, read_offset, read_size,
statistics, &header_slice, &buf, &direct_io_buffer);
statistics, &header_slice, &buf, &aligned_buf);
if (!s.ok()) {
return s;
}
@@ -218,7 +216,7 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
Slice footer_slice;
Buffer buf;
AlignedBuffer direct_io_buffer;
AlignedBuf aligned_buf;
{
TEST_SYNC_POINT("BlobFileReader::ReadFooter:ReadFromFile");
@@ -228,7 +226,7 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
const Status s =
ReadFromFile(file_reader, read_options, read_offset, read_size,
statistics, &footer_slice, &buf, &direct_io_buffer);
statistics, &footer_slice, &buf, &aligned_buf);
if (!s.ok()) {
return s;
}
@@ -259,11 +257,10 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
const ReadOptions& read_options,
uint64_t read_offset, size_t read_size,
Statistics* statistics, Slice* slice,
Buffer* buf,
AlignedBuffer* direct_io_buffer) {
Buffer* buf, AlignedBuf* aligned_buf) {
assert(slice);
assert(buf);
assert(direct_io_buffer);
assert(aligned_buf);
assert(file_reader);
@@ -280,15 +277,15 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
if (file_reader->use_direct_io()) {
constexpr char* scratch = nullptr;
AlignedBufferAllocationContext direct_io_context{direct_io_buffer};
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
&direct_io_context, &dbg);
aligned_buf, &dbg);
} else {
buf->reset(new char[read_size]);
constexpr AlignedBuf* aligned_scratch = nullptr;
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
nullptr, &dbg);
aligned_scratch, &dbg);
}
if (!s.ok()) {
@@ -296,11 +293,7 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
}
if (slice->size() != read_size) {
return Status::Corruption("Incomplete blob read from " +
file_reader->file_name() + " at offset " +
std::to_string(read_offset) + ": expected " +
std::to_string(read_size) + " bytes, got " +
std::to_string(slice->size()));
return Status::Corruption("Failed to read data from blob file");
}
return Status::OK();
@@ -334,10 +327,7 @@ Status BlobFileReader::GetBlob(
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_,
has_footer_)) {
return Status::Corruption(
"Invalid blob offset " + std::to_string(offset) + " for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name() +
" (file size " + std::to_string(file_size_) + ")");
return Status::Corruption("Invalid blob offset");
}
if (compression_type != compression_type_) {
@@ -359,7 +349,7 @@ Status BlobFileReader::GetBlob(
Slice record_slice;
Buffer buf;
AlignedBuffer direct_io_buffer;
AlignedBuf aligned_buf;
bool prefetched = false;
@@ -389,7 +379,7 @@ Status BlobFileReader::GetBlob(
const Status s =
ReadFromFile(file_reader_.get(), read_options, record_offset,
static_cast<size_t>(record_size), statistics_,
&record_slice, &buf, &direct_io_buffer);
&record_slice, &buf, &aligned_buf);
if (!s.ok()) {
return s;
}
@@ -423,83 +413,6 @@ Status BlobFileReader::GetBlob(
return Status::OK();
}
Status BlobFileReader::GetBlobRange(const ReadOptions& read_options,
const Slice& user_key, uint64_t offset,
uint64_t value_size, uint64_t range_offset,
size_t range_length,
MemoryAllocator* allocator,
std::unique_ptr<BlobContents>* result,
uint64_t* bytes_read) const {
assert(result);
// Partial reads are only meaningful for uncompressed blobs: a strict
// sub-range of a compressed record cannot be decompressed in isolation.
// Callers (BlobSource::GetBlobRange) must ensure this; enforce it here too.
if (compression_type_ != kNoCompression) {
return Status::Corruption("Cannot range-read a compressed blob");
}
const uint64_t key_size = user_key.size();
// Validate that the full value region is within the file (same check GetBlob
// performs); this guards the sub-range read below.
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_,
has_footer_)) {
return Status::Corruption(
"Invalid blob offset " + std::to_string(offset) + " for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name() +
" (file size " + std::to_string(file_size_) + ")");
}
// The requested sub-range must lie within the value.
if (range_offset > value_size || range_length > value_size - range_offset) {
return Status::InvalidArgument(
"Blob range [" + std::to_string(range_offset) + ", +" +
std::to_string(range_length) + ") out of bounds for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name());
}
// Read only the requested bytes, at the value's file position plus the range
// offset. Unlike GetBlob there is no record-header adjustment (we do not read
// the key/header) and no whole-record checksum verification (a strict
// sub-range cannot cover it -- callers that require verification take the
// full-read path instead).
const uint64_t read_offset = offset + range_offset;
const size_t read_size = range_length;
Slice range_slice;
Buffer buf;
AlignedBuffer direct_io_buffer;
TEST_SYNC_POINT("BlobFileReader::GetBlobRange:ReadFromFile");
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, read_size);
PERF_TIMER_GUARD(blob_read_time);
{
const Status s =
ReadFromFile(file_reader_.get(), read_options, read_offset, read_size,
statistics_, &range_slice, &buf, &direct_io_buffer);
if (!s.ok()) {
return s;
}
}
TEST_SYNC_POINT_CALLBACK("BlobFileReader::GetBlobRange:TamperWithResult",
&range_slice);
// The blob is uncompressed, so the bytes read are the requested value bytes;
// copy them into an owned BlobContents (no decompression).
BlobContentsCreator::Create(result, /*out_charge=*/nullptr, range_slice,
kNoCompression, allocator);
if (bytes_read) {
*bytes_read = read_size;
}
return Status::OK();
}
void BlobFileReader::MultiGetBlob(
const ReadOptions& read_options, MemoryAllocator* allocator,
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>&
@@ -531,10 +444,7 @@ void BlobFileReader::MultiGetBlob(
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_,
has_footer_)) {
*req->status = Status::Corruption(
"Invalid blob offset " + std::to_string(offset) + " for value size " +
std::to_string(value_size) + " in " + file_reader_->file_name() +
" (file size " + std::to_string(file_size_) + ")");
*req->status = Status::Corruption("Invalid blob offset");
continue;
}
if (req->compression != compression_type_) {
@@ -567,7 +477,7 @@ void BlobFileReader::MultiGetBlob(
}
Buffer buf;
AlignedBuffer direct_io_buffer;
AlignedBuf aligned_buf;
Status s;
bool direct_io = file_reader_->use_direct_io();
@@ -590,9 +500,8 @@ void BlobFileReader::MultiGetBlob(
IODebugContext dbg;
s = file_reader_->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
AlignedBufferAllocationContext direct_io_context{&direct_io_buffer};
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
&direct_io_context, &dbg);
direct_io ? &aligned_buf : nullptr, &dbg);
}
if (!s.ok()) {
for (auto& req : read_reqs) {
@@ -628,11 +537,8 @@ void BlobFileReader::MultiGetBlob(
auto& read_req = read_reqs[j++];
const auto& record_slice = read_req.result;
if (read_req.status.ok() && record_slice.size() != read_req.len) {
read_req.status = IOStatus::Corruption(
"Incomplete blob read from " + file_reader_->file_name() +
" at offset " + std::to_string(read_req.offset) + ": expected " +
std::to_string(read_req.len) + " bytes, got " +
std::to_string(record_slice.size()));
read_req.status =
IOStatus::Corruption("Failed to read data from blob file");
}
*req->status = read_req.status;
@@ -663,182 +569,6 @@ void BlobFileReader::MultiGetBlob(
}
}
void BlobFileReader::MultiGetBlobRange(
const ReadOptions& read_options,
autovector<std::pair<BlobRangeReadRequest*, std::unique_ptr<BlobContents>>>&
blob_reqs,
uint64_t* bytes_read) const {
const size_t num_blobs = blob_reqs.size();
assert(num_blobs > 0);
assert(num_blobs <= MultiGetContext::MAX_BATCH_SIZE);
// Range reads are only issued for uncompressed blob references (a strict
// sub-range of a compressed record cannot be decompressed in isolation).
// Version::MultiGetBlobLazy rejects a compressed blob index before it reaches
// here, so every request's blob index is uncompressed by contract; a
// compressed blob file therefore means the blob index disagrees with the
// file. Report that as the compression-type mismatch it is -- exactly like
// the whole-value MultiGetBlob path -- rather than as a usage error. (Unlike
// the single-read path, BlobSource has no per-request compression to check
// for a range read, so this reader-level check is the range multi path's
// compression backstop.)
if (compression_type_ != kNoCompression) {
for (auto& blob_req : blob_reqs) {
assert(blob_req.first);
assert(blob_req.first->status);
*blob_req.first->status =
Status::Corruption("Compression type mismatch when reading a blob");
}
return;
}
#ifndef NDEBUG
for (size_t i = 0; i < num_blobs - 1; ++i) {
assert(blob_reqs[i].first->offset + blob_reqs[i].first->range_offset <=
blob_reqs[i + 1].first->offset +
blob_reqs[i + 1].first->range_offset);
}
#endif // !NDEBUG
std::vector<FSReadRequest> read_reqs;
uint64_t total_len = 0;
read_reqs.reserve(num_blobs);
for (size_t i = 0; i < num_blobs; ++i) {
BlobRangeReadRequest* const req = blob_reqs[i].first;
assert(req);
assert(req->status);
const size_t key_size = req->user_key.size();
// Validate that the full value region is within the file (same check
// GetBlob performs); this guards the sub-range read below.
if (!IsValidBlobOffset(req->offset, key_size, req->value_size, file_size_,
has_footer_)) {
*req->status = Status::Corruption(
"Invalid blob offset " + std::to_string(req->offset) +
" for value size " + std::to_string(req->value_size) + " in " +
file_reader_->file_name() + " (file size " +
std::to_string(file_size_) + ")");
continue;
}
// The requested sub-range must lie within the value.
if (req->range_offset > req->value_size ||
req->range_length > req->value_size - req->range_offset) {
*req->status = Status::InvalidArgument(
"Blob range [" + std::to_string(req->range_offset) + ", +" +
std::to_string(req->range_length) +
") out of bounds for value size " + std::to_string(req->value_size) +
" in " + file_reader_->file_name());
continue;
}
// Read only the requested bytes, at the value's file position plus the
// range offset -- no record-header adjustment, no whole-record checksum.
FSReadRequest read_req;
read_req.offset = req->offset + req->range_offset;
read_req.len = req->range_length;
total_len += read_req.len;
read_reqs.emplace_back(std::move(read_req));
}
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
if (read_reqs.empty()) {
if (bytes_read) {
*bytes_read = 0;
}
return;
}
Buffer buf;
AlignedBuffer direct_io_buffer;
Status s;
bool direct_io = file_reader_->use_direct_io();
if (direct_io) {
for (size_t i = 0; i < read_reqs.size(); ++i) {
read_reqs[i].scratch = nullptr;
}
} else {
buf.reset(new char[total_len]);
std::ptrdiff_t pos = 0;
for (size_t i = 0; i < read_reqs.size(); ++i) {
read_reqs[i].scratch = buf.get() + pos;
pos += read_reqs[i].len;
}
}
TEST_SYNC_POINT("BlobFileReader::MultiGetBlobRange:ReadFromFile");
PERF_COUNTER_ADD(blob_read_count, num_blobs);
PERF_COUNTER_ADD(blob_read_byte, total_len);
IOOptions opts;
IODebugContext dbg;
s = file_reader_->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
AlignedBufferAllocationContext direct_io_context{&direct_io_buffer};
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
&direct_io_context, &dbg);
}
if (!s.ok()) {
// Batch read failed; s is written to every request's status below, so these
// per-request FS statuses carry nothing extra.
for (auto& req : read_reqs) {
req.status.PermitUncheckedError();
}
for (auto& blob_req : blob_reqs) {
BlobRangeReadRequest* const req = blob_req.first;
assert(req);
assert(req->status);
if (!req->status->IsCorruption() && !req->status->IsInvalidArgument()) {
// Avoid overwriting per-request validation errors.
*req->status = s;
}
}
return;
}
assert(s.ok());
TEST_SYNC_POINT_CALLBACK("BlobFileReader::MultiGetBlobRange:TamperWithResult",
&read_reqs);
uint64_t total_bytes = 0;
for (size_t i = 0, j = 0; i < num_blobs; ++i) {
BlobRangeReadRequest* const req = blob_reqs[i].first;
assert(req);
assert(req->status);
if (!req->status->ok()) {
continue;
}
assert(j < read_reqs.size());
auto& read_req = read_reqs[j++];
const Slice& range_slice = read_req.result;
if (read_req.status.ok() && range_slice.size() != read_req.len) {
read_req.status = IOStatus::Corruption(
"Incomplete blob range read from " + file_reader_->file_name() +
" at offset " + std::to_string(read_req.offset) + ": expected " +
std::to_string(read_req.len) + " bytes, got " +
std::to_string(range_slice.size()));
}
*req->status = read_req.status;
if (!req->status->ok()) {
continue;
}
// The blob is uncompressed, so the bytes read are exactly the requested
// value bytes; copy them into an owned BlobContents (no decompression).
BlobContentsCreator::Create(&blob_reqs[i].second, /*out_charge=*/nullptr,
range_slice, kNoCompression,
/*allocator=*/nullptr);
total_bytes += range_slice.size();
}
if (bytes_read) {
*bytes_read = total_bytes;
}
}
Status BlobFileReader::VerifyBlob(const Slice& record_slice,
const Slice& user_key, uint64_t value_size) {
PERF_TIMER_GUARD(blob_checksum_time);
+1 -33
View File
@@ -64,23 +64,6 @@ class BlobFileReader {
std::unique_ptr<BlobContents>* result,
uint64_t* bytes_read) const;
// Reads a byte sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* blob's value directly from the file, reading only those
// bytes. Unlike GetBlob it does not read the record header/key, does not
// verify the whole-record checksum (a strict sub-range cannot cover it), and
// never decompresses -- so it is only valid when this reader's blob file is
// uncompressed (checked). `offset` and `value_size` are the blob value's file
// offset and full size (from the BlobIndex); the caller must ensure
// range_offset + range_length <= value_size. On success `*result` owns a copy
// of the requested bytes and `*bytes_read` (when non-null) is the number of
// bytes read from the file.
Status GetBlobRange(const ReadOptions& read_options, const Slice& user_key,
uint64_t offset, uint64_t value_size,
uint64_t range_offset, size_t range_length,
MemoryAllocator* allocator,
std::unique_ptr<BlobContents>* result,
uint64_t* bytes_read) const;
// offsets must be sorted in ascending order by caller.
void MultiGetBlob(
const ReadOptions& read_options, MemoryAllocator* allocator,
@@ -88,21 +71,6 @@ class BlobFileReader {
blob_reqs,
uint64_t* bytes_read) const;
// Byte-range (partial) multi-read counterpart of MultiGetBlob: for each
// request reads only its sub-range [offset + range_offset, +range_length) of
// an *uncompressed* blob value, coalescing all requests into a single
// MultiRead. Unlike MultiGetBlob it reads no record header/key, verifies no
// whole-record checksum (a strict sub-range cannot cover it), and never
// decompresses -- so it is only valid when this reader's blob file is
// uncompressed (checked). Each result owns a copy of its requested bytes.
// Requests must be sorted ascending by effective read offset (offset +
// range_offset) by the caller.
void MultiGetBlobRange(
const ReadOptions& read_options,
autovector<std::pair<BlobRangeReadRequest*,
std::unique_ptr<BlobContents>>>& blob_reqs,
uint64_t* bytes_read) const;
CompressionType GetCompressionType() const { return compression_type_; }
uint64_t GetFileSize() const { return file_size_; }
@@ -140,7 +108,7 @@ class BlobFileReader {
const ReadOptions& read_options,
uint64_t read_offset, size_t read_size,
Statistics* statistics, Slice* slice, Buffer* buf,
AlignedBuffer* direct_io_buffer);
AlignedBuf* aligned_buf);
static Status VerifyBlob(const Slice& record_slice, const Slice& user_key,
uint64_t value_size);
+1 -368
View File
@@ -7,12 +7,10 @@
#include <cassert>
#include <string>
#include <utility>
#include "db/blob/blob_contents.h"
#include "db/blob/blob_log_format.h"
#include "db/blob/blob_log_writer.h"
#include "env/composite_env_wrapper.h"
#include "env/mock_env.h"
#include "file/filename.h"
#include "file/read_write_util.h"
@@ -138,167 +136,6 @@ class BlobFileReaderTest : public testing::Test {
std::unique_ptr<Env> mock_env_;
};
namespace {
// Returns a stale path-level size for one target blob file while leaving the
// opened file handle untouched. This lets the test verify
// BlobFileReader::Create prefers the opened handle's size when the path stat
// lags behind.
class StalePathSizeFileSystem : public FileSystemWrapper {
public:
static const char* kClassName() { return "StalePathSizeFileSystem"; }
StalePathSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string stale_path)
: FileSystemWrapper(target), stale_path_(std::move(stale_path)) {}
const char* Name() const override { return kClassName(); }
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (fname == stale_path_) {
*file_size = 0;
return IOStatus::OK();
}
return FileSystemWrapper::GetFileSize(fname, options, file_size, dbg);
}
private:
const std::string stale_path_;
};
// Makes opened blob file handles fail GetFileSize() with a hard error so the
// reader path can verify that such errors are propagated directly.
class FailingOpenFileSizeRandomAccessFile
: public FSRandomAccessFileOwnerWrapper {
public:
explicit FailingOpenFileSizeRandomAccessFile(
std::unique_ptr<FSRandomAccessFile>&& target)
: FSRandomAccessFileOwnerWrapper(std::move(target)) {}
IOStatus GetFileSize(uint64_t* /*result*/) override {
return IOStatus::IOError("open file size failed");
}
};
// Makes opened blob file handles report GetFileSize() as unsupported so the
// reader path exercises its path-level fallback logic.
class NotSupportedOpenFileSizeRandomAccessFile
: public FSRandomAccessFileOwnerWrapper {
public:
explicit NotSupportedOpenFileSizeRandomAccessFile(
std::unique_ptr<FSRandomAccessFile>&& target)
: FSRandomAccessFileOwnerWrapper(std::move(target)) {}
IOStatus GetFileSize(uint64_t* /*result*/) override {
return IOStatus::NotSupported("open file size not supported");
}
};
class OpenFileSizeFileSystem : public FileSystemWrapper {
public:
OpenFileSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: FileSystemWrapper(target), target_path_(std::move(target_path)) {}
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& options,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
std::unique_ptr<FSRandomAccessFile> file;
IOStatus s =
FileSystemWrapper::NewRandomAccessFile(fname, options, &file, dbg);
if (!s.ok()) {
return IOStatus(std::move(s));
}
if (fname == target_path_) {
*result = WrapTargetFile(std::move(file));
} else {
*result = std::move(file);
}
return IOStatus::OK();
}
protected:
virtual std::unique_ptr<FSRandomAccessFile> WrapTargetFile(
std::unique_ptr<FSRandomAccessFile>&& file) = 0;
private:
const std::string target_path_;
};
// Wraps the target blob file in FailingOpenFileSizeRandomAccessFile while
// leaving all other files alone.
class FailingOpenFileSizeFileSystem : public OpenFileSizeFileSystem {
public:
static const char* kClassName() { return "FailingOpenFileSizeFileSystem"; }
FailingOpenFileSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: OpenFileSizeFileSystem(target, std::move(target_path)) {}
const char* Name() const override { return kClassName(); }
private:
std::unique_ptr<FSRandomAccessFile> WrapTargetFile(
std::unique_ptr<FSRandomAccessFile>&& file) override {
return std::unique_ptr<FSRandomAccessFile>(
new FailingOpenFileSizeRandomAccessFile(std::move(file)));
}
};
// Wraps the target blob file in NotSupportedOpenFileSizeRandomAccessFile while
// leaving all other files alone.
class NotSupportedOpenFileSizeFileSystem : public OpenFileSizeFileSystem {
public:
static const char* kClassName() {
return "NotSupportedOpenFileSizeFileSystem";
}
NotSupportedOpenFileSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: OpenFileSizeFileSystem(target, std::move(target_path)) {}
const char* Name() const override { return kClassName(); }
private:
std::unique_ptr<FSRandomAccessFile> WrapTargetFile(
std::unique_ptr<FSRandomAccessFile>&& file) override {
return std::unique_ptr<FSRandomAccessFile>(
new NotSupportedOpenFileSizeRandomAccessFile(std::move(file)));
}
};
// Forces the target blob file onto the NotSupported open-handle-size branch and
// then fails the fallback path-level GetFileSize() call.
class FailingFallbackPathSizeFileSystem
: public NotSupportedOpenFileSizeFileSystem {
public:
static const char* kClassName() {
return "FailingFallbackPathSizeFileSystem";
}
FailingFallbackPathSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: NotSupportedOpenFileSizeFileSystem(target, target_path),
target_path_(std::move(target_path)) {}
const char* Name() const override { return kClassName(); }
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (fname == target_path_) {
return IOStatus::IOError("fallback path size failed");
}
return FileSystemWrapper::GetFileSize(fname, options, file_size, dbg);
}
private:
const std::string target_path_;
};
} // namespace
TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
Options options;
options.env = mock_env_.get();
@@ -591,211 +428,6 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
}
}
// Verifies Create() prefers the file size reported by the opened handle over a
// stale path-level size query by returning a readable reader even when the path
// stat intentionally reports 0 for the blob file.
TEST_F(BlobFileReaderTest, CreateReaderUsesOpenedFileSizeWhenPathSizeIsStale) {
const std::string db_path = test::PerThreadDBPath(
mock_env_.get(),
"BlobFileReaderTest_CreateReaderUsesOpenedFileSizeWhenPathSizeIsStale");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto stale_size_fs = std::make_shared<StalePathSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper stale_size_env(mock_env_.get(), stale_size_fs);
Options options;
options.env = &stale_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
read_options.verify_checksums = false;
ASSERT_OK(BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader));
ASSERT_NE(reader, nullptr);
std::unique_ptr<BlobContents> value;
uint64_t bytes_read = 0;
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
kNoCompression,
/*prefetch_buffer=*/nullptr,
/*allocator=*/nullptr, &value, &bytes_read));
ASSERT_NE(value, nullptr);
ASSERT_EQ(value->data(), Slice(blob));
ASSERT_EQ(bytes_read, blob_size);
}
// Verifies Create() falls back to the path-level GetFileSize() call when the
// opened file handle reports that size queries are unsupported.
TEST_F(BlobFileReaderTest,
CreateReaderFallsBackToPathSizeWhenOpenedFileSizeNotSupported) {
const std::string db_path = test::PerThreadDBPath(
mock_env_.get(),
"BlobFileReaderTest_"
"CreateReaderFallsBackToPathSizeWhenOpenedFileSizeNotSupported");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto not_supported_open_file_size_fs =
std::make_shared<NotSupportedOpenFileSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper not_supported_open_file_size_env(
mock_env_.get(), not_supported_open_file_size_fs);
Options options;
options.env = &not_supported_open_file_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
read_options.verify_checksums = false;
ASSERT_OK(BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader));
ASSERT_NE(reader, nullptr);
std::unique_ptr<BlobContents> value;
uint64_t bytes_read = 0;
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
kNoCompression,
/*prefetch_buffer=*/nullptr,
/*allocator=*/nullptr, &value, &bytes_read));
ASSERT_NE(value, nullptr);
ASSERT_EQ(value->data(), Slice(blob));
ASSERT_EQ(bytes_read, blob_size);
}
// Verifies Create() propagates a real error from the opened file handle's
// GetFileSize() rather than masking it with any path-level fallback.
TEST_F(BlobFileReaderTest, CreateReaderPropagatesOpenedFileSizeError) {
const std::string db_path = test::PerThreadDBPath(
mock_env_.get(),
"BlobFileReaderTest_CreateReaderPropagatesOpenedFileSizeError");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto failing_open_file_size_fs =
std::make_shared<FailingOpenFileSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper failing_open_file_size_env(mock_env_.get(),
failing_open_file_size_fs);
Options options;
options.env = &failing_open_file_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
Status s = BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader);
ASSERT_TRUE(s.IsIOError());
ASSERT_EQ(reader, nullptr);
}
// Verifies Create() also propagates errors from the path-level fallback size
// query when the opened file handle only reports NotSupported for GetFileSize.
TEST_F(
BlobFileReaderTest,
CreateReaderPropagatesFallbackPathSizeErrorWhenOpenedFileSizeNotSupported) {
const std::string db_path =
test::PerThreadDBPath(mock_env_.get(),
"BlobFileReaderTest_"
"CreateReaderPropagatesFallbackPathSizeErrorWhenOpe"
"nedFileSizeNotSupported");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto failing_fallback_path_size_fs =
std::make_shared<FailingFallbackPathSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper failing_fallback_path_size_env(
mock_env_.get(), failing_fallback_path_size_fs);
Options options;
options.env = &failing_fallback_path_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
Status s = BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader);
ASSERT_TRUE(s.IsIOError());
ASSERT_EQ(reader, nullptr);
}
TEST_F(BlobFileReaderTest, Malformed) {
// Write a blob file consisting of nothing but a header, and make sure we
// detect the error when we open it for reading
@@ -1217,6 +849,7 @@ class BlobFileReaderIOErrorTest
INSTANTIATE_TEST_CASE_P(BlobFileReaderTest, BlobFileReaderIOErrorTest,
::testing::ValuesIn(std::vector<std::string>{
"BlobFileReader::OpenFile:GetFileSize",
"BlobFileReader::OpenFile:NewRandomAccessFile",
"BlobFileReader::ReadHeader:ReadFromFile",
"BlobFileReader::ReadFooter:ReadFromFile",
+1 -6
View File
@@ -32,9 +32,6 @@ Status BlobGarbageMeter::GetBlobReferenceDetails(const ParsedInternalKey& ikey,
if (blob_index.IsInlined() || blob_index.HasTTL()) {
return Status::Corruption("Unexpected TTL/inlined blob index");
}
if (blob_index.IsSameFile()) {
return Status::OK();
}
*blob_file_number = blob_index.file_number();
*bytes =
@@ -128,9 +125,7 @@ Status BlobGarbageMeter::ProcessEntityBlobReferences(
!s.ok()) {
return s;
}
if (file_number != kInvalidBlobFileNumber) {
AddFlow(file_number, blob_bytes, is_inflow);
}
AddFlow(file_number, blob_bytes, is_inflow);
return Status::OK();
});
}
-18
View File
@@ -151,24 +151,6 @@ TEST(BlobGarbageMeterTest, PlainValue) {
ASSERT_TRUE(blob_garbage_meter.flows().empty());
}
TEST(BlobGarbageMeterTest, SameFileBlobIndex) {
constexpr char user_key[] = "user_key";
constexpr SequenceNumber seq = 123;
const InternalKey key(user_key, seq, kTypeBlobIndex);
const Slice key_slice = key.Encode();
std::string value;
BlobIndex::EncodeBlob(&value, kCurrentFileBlobIndexFileNumber,
/*offset=*/123, /*size=*/456, kNoCompression);
BlobGarbageMeter blob_garbage_meter;
ASSERT_OK(blob_garbage_meter.ProcessInFlow(key_slice, value));
ASSERT_OK(blob_garbage_meter.ProcessOutFlow(key_slice, value));
ASSERT_TRUE(blob_garbage_meter.flows().empty());
}
TEST(BlobGarbageMeterTest, CorruptInternalKey) {
constexpr char corrupt_key[] = "i_am_corrupt";
const Slice key_slice(corrupt_key);
-435
View File
@@ -1,435 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "db/blob/blob_gen2_format.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <string>
#include <vector>
#include "file/random_access_file_reader.h"
#include "file/writable_file_writer.h"
#include "rocksdb/options.h"
#include "rocksdb/slice.h"
#include "table/format.h"
#include "util/cast_util.h"
#include "util/coding.h"
#include "util/crc32c.h"
namespace ROCKSDB_NAMESPACE {
namespace {
// Verifies a SimpleGen2Blob record's builtin checksum: the stored 4-byte
// checksum (at record[payload_size + 1 ..], context-modified by the record's
// file offset) against the checksum computed over the payload plus its 1-byte
// compression marker. Returns OK, or a Corruption carrying debugging
// information -- stored (with the per-offset context modifier removed),
// computed, and checksum type, with CRC values unmasked so they can be compared
// against a reference. Mirrors VerifyBlockChecksum() for the block-based
// format, since a SimpleGen2Blob record's trailer uses the same
// builtin-checksum scheme; shared by the scalar and batched readers so both
// report the same diagnostics.
Status VerifySimpleGen2BlobChecksum(ChecksumType checksum_type,
uint32_t base_context_checksum,
const char* record, size_t payload_size,
uint64_t record_offset,
const std::string& file_name) {
uint32_t stored = DecodeFixed32(record + payload_size + 1);
const uint32_t modifier =
ChecksumModifierForContext(base_context_checksum, record_offset);
stored -= modifier;
const uint32_t computed = ComputeBuiltinChecksumWithLastByte(
checksum_type, record, payload_size, record[payload_size]);
if (stored == computed) {
return Status::OK();
}
// Unmask CRC values (as VerifyBlockChecksum does) so a reader of the error
// can compare against a reference checksum.
uint32_t stored_for_msg = stored;
uint32_t computed_for_msg = computed;
if (checksum_type == kCRC32c) {
stored_for_msg = crc32c::Unmask(stored);
computed_for_msg = crc32c::Unmask(computed);
}
return Status::Corruption("SimpleGen2 blob record checksum mismatch: stored" +
std::string(modifier ? "(context removed)" : "") +
" = " + std::to_string(stored_for_msg) +
", computed = " + std::to_string(computed_for_msg) +
", type = " + std::to_string(checksum_type) +
" in " + file_name + " offset " +
std::to_string(record_offset) + " size " +
std::to_string(payload_size));
}
} // namespace
Status ReadAndVerifySimpleGen2BlobRecord(
const ReadOptions& read_options, RandomAccessFileReader* file,
uint64_t record_offset, size_t payload_size, size_t record_size,
ChecksumType checksum_type, uint32_t base_context_checksum,
CompressionType expected_compression, char* buf) {
assert(file != nullptr);
assert(buf != nullptr);
assert(record_size == payload_size + kSimpleGen2BlobTrailerSize);
Slice result;
IOOptions opts;
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file->Read(opts, record_offset, record_size, &result, buf, nullptr,
&dbg);
}
if (!s.ok()) {
return s;
}
if (result.size() != record_size) {
return Status::Corruption("Incomplete blob record read from " +
file->file_name() + " at offset " +
std::to_string(record_offset) + ": expected " +
std::to_string(record_size) + " bytes, got " +
std::to_string(result.size()));
}
// With mmap reads the data lands outside `buf`; copy it in so the caller can
// rely on `buf` owning the bytes (this is the only copy on the mmap path).
// TODO: fix this extra memcpy in the mmap case
if (result.data() != buf) {
memcpy(buf, result.data(), record_size);
}
const char* record = buf;
const CompressionType compression =
static_cast<CompressionType>(record[payload_size]);
if (compression != expected_compression) {
return Status::Corruption(
"Blob record compression mismatch in " + file->file_name() +
" at offset " + std::to_string(record_offset) + ": index expects " +
std::to_string(static_cast<int>(expected_compression)) +
", record has " + std::to_string(static_cast<int>(compression)));
}
if (read_options.verify_checksums) {
const Status checksum_status = VerifySimpleGen2BlobChecksum(
checksum_type, base_context_checksum, record, payload_size,
record_offset, file->file_name());
if (!checksum_status.ok()) {
return checksum_status;
}
}
// The compression marker matched the blob index above, but SimpleGen2Blob
// payloads are currently written uncompressed only (see
// WriteSimpleGen2BlobRecord), so a record that actually carries a compression
// type is not readable here. This guards a corrupt or forward-incompatible
// record whose (compressed) marker happened to agree with a (compressed)
// index.
if (compression != kNoCompression) {
return Status::Corruption("Blob record compression " +
std::to_string(static_cast<int>(compression)) +
" is not supported in " + file->file_name() +
" at offset " + std::to_string(record_offset));
}
return Status::OK();
}
Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
RandomAccessFileReader* file,
uint64_t record_offset, size_t payload_size,
uint64_t range_offset, size_t range_length,
CompressionType expected_compression,
char* buf) {
assert(file != nullptr);
assert(buf != nullptr);
// A strict sub-range of a compressed payload cannot be decompressed in
// isolation; callers resolve such payloads whole and slice instead.
if (expected_compression != kNoCompression) {
return Status::Corruption(
"Cannot range-read a compressed blob (compression " +
std::to_string(static_cast<int>(expected_compression)) + ") in " +
file->file_name() + " at offset " + std::to_string(record_offset));
}
// The requested sub-range must lie within the payload (the trailer that
// follows the payload is never part of a range read).
if (range_offset > payload_size ||
range_length > payload_size - range_offset) {
return Status::InvalidArgument(
"Blob range [" + std::to_string(range_offset) + ", +" +
std::to_string(range_length) + ") out of bounds for payload size " +
std::to_string(payload_size) + " in " + file->file_name() +
" at offset " + std::to_string(record_offset));
}
Slice result;
IOOptions opts;
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file->Read(opts, record_offset + range_offset, range_length, &result,
buf, nullptr, &dbg);
}
if (!s.ok()) {
return s;
}
if (result.size() != range_length) {
return Status::Corruption("Incomplete blob range read from " +
file->file_name() + " at offset " +
std::to_string(record_offset + range_offset) +
": expected " + std::to_string(range_length) +
" bytes, got " + std::to_string(result.size()));
}
// With mmap reads the data lands outside `buf`; copy it in so the caller can
// rely on `buf` owning the bytes (the only copy on the mmap path).
if (result.data() != buf) {
memcpy(buf, result.data(), range_length);
}
// No trailer read and no checksum verification: a strict sub-range cannot
// cover the record's checksum (callers that require it read the whole
// record).
return Status::OK();
}
namespace {
// One record's read description for the batched SimpleGen2Blob readers below:
// where/how much to read, where to put it, and where to report the outcome.
struct Gen2ReadSlot {
uint64_t offset = 0;
size_t len = 0;
char* buf = nullptr;
Status* status = nullptr;
};
// Issues a single MultiRead for the records described by `slots` and copies
// each result into its buf, setting *slots[i].status. Shared by the
// whole-record and range batch readers; per-record format checks (compression
// marker / checksum) are done by the whole-record caller afterwards from the
// (now populated) buf.
void MultiReadGen2(const ReadOptions& read_options,
RandomAccessFileReader* file,
std::vector<Gen2ReadSlot>& slots) {
assert(file != nullptr);
assert(!slots.empty());
// RandomAccessFileReader::MultiRead requires the requests sorted ascending by
// file offset -- it asserts this, and in direct-I/O mode it aligns and merges
// only consecutive requests, so unsorted input reads the wrong bytes. The
// callers group records by SST but not by offset, so sort here. Each slot
// carries its own output buf/status pointer, so sorting does not disturb
// result routing.
std::sort(slots.begin(), slots.end(),
[](const Gen2ReadSlot& a, const Gen2ReadSlot& b) {
return a.offset < b.offset;
});
const size_t num = slots.size();
std::vector<FSReadRequest> read_reqs;
read_reqs.reserve(num);
for (size_t i = 0; i < num; ++i) {
FSReadRequest read_req;
read_req.offset = slots[i].offset;
read_req.len = slots[i].len;
read_reqs.emplace_back(std::move(read_req));
}
AlignedBuffer direct_io_buffer;
const bool direct_io = file->use_direct_io();
if (direct_io) {
for (size_t i = 0; i < num; ++i) {
read_reqs[i].scratch = nullptr;
}
} else {
for (size_t i = 0; i < num; ++i) {
read_reqs[i].scratch = slots[i].buf;
}
}
IOOptions opts;
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
AlignedBufferAllocationContext direct_io_context{&direct_io_buffer};
s = file->MultiRead(opts, read_reqs.data(), read_reqs.size(),
&direct_io_context, &dbg);
}
if (!s.ok()) {
// Batch read failed; s is written to every slot's status below, so these
// per-request FS statuses carry nothing extra.
for (auto& read_req : read_reqs) {
read_req.status.PermitUncheckedError();
}
for (size_t i = 0; i < num; ++i) {
*slots[i].status = s;
}
return;
}
for (size_t i = 0; i < num; ++i) {
FSReadRequest& read_req = read_reqs[i];
if (read_req.status.ok() && read_req.result.size() != read_req.len) {
read_req.status = IOStatus::Corruption(
"Incomplete blob record read from " + file->file_name() +
" at offset " + std::to_string(read_req.offset) + ": expected " +
std::to_string(read_req.len) + " bytes, got " +
std::to_string(read_req.result.size()));
}
*slots[i].status = read_req.status;
if (!slots[i].status->ok()) {
continue;
}
// With mmap/direct reads the data lands outside the caller's buf; copy it
// in so the caller can rely on buf owning the bytes.
if (read_req.result.data() != slots[i].buf) {
memcpy(slots[i].buf, read_req.result.data(), read_req.len);
}
}
}
} // namespace
void ReadAndVerifySimpleGen2BlobRecords(const ReadOptions& read_options,
RandomAccessFileReader* file,
ChecksumType checksum_type,
uint32_t base_context_checksum,
size_t num_records,
SimpleGen2RecordReadRequest* reqs) {
assert(file != nullptr);
assert(num_records > 0);
std::vector<Gen2ReadSlot> slots;
slots.reserve(num_records);
for (size_t i = 0; i < num_records; ++i) {
assert(reqs[i].buf != nullptr);
assert(reqs[i].status != nullptr);
assert(reqs[i].record_size ==
reqs[i].payload_size + kSimpleGen2BlobTrailerSize);
slots.push_back({reqs[i].record_offset, reqs[i].record_size, reqs[i].buf,
reqs[i].status});
}
MultiReadGen2(read_options, file, slots);
for (size_t i = 0; i < num_records; ++i) {
if (!reqs[i].status->ok()) {
continue;
}
const char* record = reqs[i].buf;
const size_t payload_size = reqs[i].payload_size;
const CompressionType compression =
static_cast<CompressionType>(record[payload_size]);
if (compression != reqs[i].expected_compression) {
*reqs[i].status = Status::Corruption(
"Blob record compression mismatch in " + file->file_name() +
" at offset " + std::to_string(reqs[i].record_offset) +
": index expects " +
std::to_string(static_cast<int>(reqs[i].expected_compression)) +
", record has " + std::to_string(static_cast<int>(compression)));
continue;
}
if (read_options.verify_checksums) {
*reqs[i].status = VerifySimpleGen2BlobChecksum(
checksum_type, base_context_checksum, record, payload_size,
reqs[i].record_offset, file->file_name());
if (!reqs[i].status->ok()) {
continue;
}
}
// See ReadAndVerifySimpleGen2BlobRecord: the marker matched the index, but
// only uncompressed SimpleGen2Blob payloads are currently supported.
if (compression != kNoCompression) {
*reqs[i].status = Status::Corruption(
"Blob record compression " +
std::to_string(static_cast<int>(compression)) +
" is not supported in " + file->file_name() + " at offset " +
std::to_string(reqs[i].record_offset));
continue;
}
}
}
void ReadSimpleGen2BlobRanges(const ReadOptions& read_options,
RandomAccessFileReader* file, size_t num_records,
SimpleGen2RangeReadRequest* reqs) {
assert(file != nullptr);
assert(num_records > 0);
std::vector<Gen2ReadSlot> slots;
slots.reserve(num_records);
for (size_t i = 0; i < num_records; ++i) {
assert(reqs[i].buf != nullptr);
assert(reqs[i].status != nullptr);
// A strict sub-range of a compressed payload cannot be decompressed in
// isolation; callers resolve such payloads whole and slice instead.
if (reqs[i].expected_compression != kNoCompression) {
*reqs[i].status = Status::Corruption(
"Cannot range-read a compressed blob (compression " +
std::to_string(static_cast<int>(reqs[i].expected_compression)) +
") in " + file->file_name() + " at offset " +
std::to_string(reqs[i].record_offset));
continue;
}
// The requested sub-range must lie within the payload (the trailer that
// follows the payload is never part of a range read).
if (reqs[i].range_offset > reqs[i].payload_size ||
reqs[i].range_length > reqs[i].payload_size - reqs[i].range_offset) {
*reqs[i].status = Status::InvalidArgument(
"Blob range [" + std::to_string(reqs[i].range_offset) + ", +" +
std::to_string(reqs[i].range_length) +
") out of bounds for payload size " +
std::to_string(reqs[i].payload_size) + " in " + file->file_name() +
" at offset " + std::to_string(reqs[i].record_offset));
continue;
}
slots.push_back({reqs[i].record_offset + reqs[i].range_offset,
reqs[i].range_length, reqs[i].buf, reqs[i].status});
}
if (slots.empty()) {
return;
}
// slots only covers requests that passed validation above; a failed
// validation left *reqs[i].status non-OK and is skipped here.
MultiReadGen2(read_options, file, slots);
}
IOStatus WriteSimpleGen2BlobRecord(WritableFileWriter* file,
const WriteOptions& write_options,
ChecksumType checksum_type,
uint32_t base_context_checksum,
uint64_t record_offset, const Slice& payload,
CompressionType compression) {
assert(file != nullptr);
// Placeholder for future embedded blob compression support; only
// uncompressed payloads are currently written.
assert(compression == kNoCompression);
std::array<char, kSimpleGen2BlobTrailerSize> trailer;
trailer[0] = lossless_cast<char>(compression);
uint32_t checksum = ComputeBuiltinChecksumWithLastByte(
checksum_type, payload.data(), payload.size(), /*last_byte=*/trailer[0]);
checksum += ChecksumModifierForContext(base_context_checksum, record_offset);
EncodeFixed32(trailer.data() + 1, checksum);
IOOptions opts;
IOStatus io_s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!io_s.ok()) {
return io_s;
}
if (!payload.empty()) {
io_s = file->Append(opts, payload);
if (!io_s.ok()) {
return io_s;
}
}
return file->Append(opts, Slice(trailer.data(), trailer.size()));
}
} // namespace ROCKSDB_NAMESPACE
-161
View File
@@ -1,161 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include <cstddef>
#include <cstdint>
#include "cache/cache_key.h"
#include "rocksdb/compression_type.h"
#include "rocksdb/io_status.h"
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/status.h"
namespace ROCKSDB_NAMESPACE {
struct ReadOptions;
struct WriteOptions;
class RandomAccessFileReader;
class WritableFileWriter;
class Slice;
enum ChecksumType : char;
// "SimpleGen2Blob" is the second-generation on-disk format for a blob payload
// referenced by a same-file / context-relative blob index. A record is just the
// payload bytes followed by a small block-style trailer:
//
// <payload bytes> <1-byte compression marker> <4-byte checksum>
//
// The checksum is a builtin block checksum over (payload + compression marker),
// context-modified by the record's absolute file offset (see
// ChecksumModifierForContext / ComputeBuiltinChecksumWithLastByte in
// table/format.h) so that an identical payload at a different offset gets a
// different stored checksum. Payloads are currently uncompressed only.
//
// This format is deliberately decoupled from any particular file type: the same
// record shape is read out of SST files (embedded blobs) and is intended to be
// reused by other blob-bearing files. The only file-type-specific inputs are
// the byte source (a RandomAccessFileReader) and the checksum context (checksum
// type + base context checksum), both passed in by the caller.
inline constexpr size_t kSimpleGen2BlobTrailerSize = 5;
// Cache key for the SimpleGen2Blob record at byte `record_offset` within a file
// whose base cache key is `base_cache_key`. Delegates to the shared
// OffsetableCacheKey::WithOffsetForMinSizeRecord scheme -- the exact same
// scheme block-based SST data blocks use via BlockBasedTable::GetCacheKey --
// which is valid because a SimpleGen2Blob record always carries a >= 5-byte
// trailer. As a result the cache key is collision-free with the file's data
// blocks even when the blob cache and block cache are the same cache, with no
// separately maintained algorithm to drift out of sync.
inline CacheKey GetSimpleGen2BlobCacheKey(
const OffsetableCacheKey& base_cache_key, uint64_t record_offset) {
return base_cache_key.WithOffsetForMinSizeRecord(record_offset);
}
// Reads a SimpleGen2Blob record of `record_size` bytes (which must equal
// `payload_size + kSimpleGen2BlobTrailerSize`) from `file` at `record_offset`
// into `buf` (capacity >= record_size) and verifies its compression marker and,
// when read_options.verify_checksums is set, its context-modified checksum.
//
// `checksum_type` and `base_context_checksum` are the file's checksum context
// (e.g. from the SST footer). `expected_compression` is the compression type
// the caller expects the record to carry (from the blob index); it must match
// the record's marker, and currently must be kNoCompression.
//
// On success, buf[0, payload_size) holds the verified, uncompressed payload
// (the trailer remains at the tail of `buf` and can be ignored).
Status ReadAndVerifySimpleGen2BlobRecord(
const ReadOptions& read_options, RandomAccessFileReader* file,
uint64_t record_offset, size_t payload_size, size_t record_size,
ChecksumType checksum_type, uint32_t base_context_checksum,
CompressionType expected_compression, char* buf);
// Reads a byte sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* SimpleGen2Blob payload directly into `buf` (capacity >=
// range_length), reading only those bytes. Unlike
// ReadAndVerifySimpleGen2BlobRecord it does not read the trailer and does not
// verify the record's checksum (a strict sub-range cannot cover it); callers
// that require verification read the whole record instead. `payload_size` is
// the full payload size (from the blob index); the caller must ensure
// range_offset + range_length <= payload_size. `expected_compression` must be
// kNoCompression (a strict sub-range of a compressed payload cannot be
// decompressed in isolation).
//
// On success, buf[0, range_length) holds the requested payload bytes.
Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
RandomAccessFileReader* file,
uint64_t record_offset, size_t payload_size,
uint64_t range_offset, size_t range_length,
CompressionType expected_compression, char* buf);
// One whole-record request in a batched SimpleGen2Blob read (see
// ReadAndVerifySimpleGen2BlobRecords). Mirrors the scalar
// ReadAndVerifySimpleGen2BlobRecord inputs, with the read bytes going into
// caller-provided `buf` (capacity >= record_size) and the per-record outcome in
// `*status`.
struct SimpleGen2RecordReadRequest {
uint64_t record_offset = 0;
size_t payload_size = 0;
size_t record_size = 0; // == payload_size + kSimpleGen2BlobTrailerSize
CompressionType expected_compression = kNoCompression;
char* buf = nullptr;
Status* status = nullptr;
};
// One sub-range request in a batched SimpleGen2Blob range read (see
// ReadSimpleGen2BlobRanges). Mirrors the scalar ReadSimpleGen2BlobRange inputs.
struct SimpleGen2RangeReadRequest {
uint64_t record_offset = 0;
size_t payload_size = 0;
uint64_t range_offset = 0;
size_t range_length = 0;
CompressionType expected_compression = kNoCompression;
char* buf = nullptr;
Status* status = nullptr;
};
// Batch counterpart of ReadAndVerifySimpleGen2BlobRecord: reads `num_records`
// whole records from `file` in a single MultiRead, then per record verifies the
// compression marker and (when read_options.verify_checksums) the
// context-modified checksum, copying the record into reqs[i].buf. Per-record
// outcome is written to *reqs[i].status; on success reqs[i].buf[0,
// payload_size) holds the verified payload (the trailer sits unused at the
// tail). Requests may be given in any order; they are sorted internally, as
// RandomAccessFileReader::MultiRead requires ascending offsets.
void ReadAndVerifySimpleGen2BlobRecords(const ReadOptions& read_options,
RandomAccessFileReader* file,
ChecksumType checksum_type,
uint32_t base_context_checksum,
size_t num_records,
SimpleGen2RecordReadRequest* reqs);
// Batch counterpart of ReadSimpleGen2BlobRange: reads the requested sub-range
// of `num_records` *uncompressed* payloads from `file` in a single MultiRead
// (no trailer read, no checksum). Per-record outcome is in *reqs[i].status; on
// success reqs[i].buf[0, range_length) holds the requested bytes. Requests may
// be given in any order; they are sorted internally, as
// RandomAccessFileReader::MultiRead requires ascending offsets.
void ReadSimpleGen2BlobRanges(const ReadOptions& read_options,
RandomAccessFileReader* file, size_t num_records,
SimpleGen2RangeReadRequest* reqs);
// Writes a SimpleGen2Blob record for `payload` at the current end of `file`,
// which the caller asserts is byte offset `record_offset`. Appends the payload
// bytes followed by the 5-byte trailer (compression marker + context-modified
// builtin checksum), mirroring ReadAndVerifySimpleGen2BlobRecord so the on-disk
// record format lives in one module.
//
// `checksum_type` and `base_context_checksum` are the file's checksum context
// (e.g. from the SST footer). `compression` is the payload's compression type,
// which currently must be kNoCompression.
IOStatus WriteSimpleGen2BlobRecord(WritableFileWriter* file,
const WriteOptions& write_options,
ChecksumType checksum_type,
uint32_t base_context_checksum,
uint64_t record_offset, const Slice& payload,
CompressionType compression);
} // namespace ROCKSDB_NAMESPACE
+2 -16
View File
@@ -7,7 +7,6 @@
#include <sstream>
#include <string>
#include "db/blob/blob_constants.h"
#include "rocksdb/compression_type.h"
#include "util/coding.h"
#include "util/compression.h"
@@ -58,14 +57,6 @@ class BlobIndex {
bool IsInlined() const { return type_ == Type::kInlinedTTL; }
// True for a blob record stored in the same physical file as the table entry.
// Only embedded-blob SST reader/writer paths should interpret file number
// zero this way; generic blob metadata treats it as invalid.
bool IsSameFile() const {
return (type_ == Type::kBlob || type_ == Type::kBlobTTL) &&
file_number_ == kCurrentFileBlobIndexFileNumber;
}
bool HasTTL() const {
return type_ == Type::kInlinedTTL || type_ == Type::kBlobTTL;
}
@@ -134,13 +125,8 @@ class BlobIndex {
if (IsInlined()) {
oss << "[inlined blob] value:" << value_.ToString(output_hex);
} else {
oss << "[blob ref] file:";
if (IsSameFile()) {
oss << "same";
} else {
oss << file_number_;
}
oss << " offset:" << offset_ << " size:" << size_
oss << "[blob ref] file:" << file_number_ << " offset:" << offset_
<< " size:" << size_
<< " compression: " << CompressionTypeToString(compression_);
}
-45
View File
@@ -18,7 +18,6 @@ namespace ROCKSDB_NAMESPACE {
// BlobFileReader::MultiGetBlob.
struct BlobReadRequest {
// User key to lookup the paired blob
// TODO: consider simplifying to copied Slice?
const Slice* user_key = nullptr;
// File offset in bytes
@@ -56,48 +55,4 @@ using BlobFileReadRequests =
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
autovector<BlobReadRequest>>;
// A byte-range (partial) blob read request for the lazy blob-read multi
// primitives (BlobFileReader::MultiGetBlobRange /
// BlobSource::MultiGetBlobRange). Unlike BlobReadRequest (whole value), it
// reads only the sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* blob value, skipping whole-record checksum verification and
// blob-cache population -- the multi-read counterpart of
// BlobSource::GetBlobRange / BlobFileReader::GetBlobRange.
struct BlobRangeReadRequest {
// User key that pairs with the blob value (used for offset validation).
Slice user_key;
// The blob value's file offset and full (logical == on-disk, uncompressed)
// size, from the BlobIndex.
uint64_t offset = 0;
uint64_t value_size = 0;
// The requested sub-range within the value. `range_length` is the number of
// bytes to read; the caller must ensure range_offset + range_length <=
// value_size (it clamps before building the request).
uint64_t range_offset = 0;
size_t range_length = 0;
// Output parameter set to point to the requested bytes, and the read status.
PinnableSlice* result = nullptr;
Status* status = nullptr;
BlobRangeReadRequest(Slice _user_key, uint64_t _offset, uint64_t _value_size,
uint64_t _range_offset, size_t _range_length,
PinnableSlice* _result, Status* _status)
: user_key(_user_key),
offset(_offset),
value_size(_value_size),
range_offset(_range_offset),
range_length(_range_length),
result(_result),
status(_status) {}
BlobRangeReadRequest() = default;
};
using BlobFileRangeReadRequests =
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
autovector<BlobRangeReadRequest>>;
} // namespace ROCKSDB_NAMESPACE
+48 -713
View File
@@ -7,16 +7,12 @@
#include <cassert>
#include <string>
#include <vector>
#include "cache/cache_reservation_manager.h"
#include "cache/charged_cache.h"
#include "db/blob/blob_contents.h"
#include "db/blob/blob_file_reader.h"
#include "db/blob/blob_gen2_format.h"
#include "db/blob/blob_log_format.h"
#include "file/random_access_file_reader.h"
#include "memory/memory_allocator_impl.h"
#include "monitoring/statistics_impl.h"
#include "options/cf_options.h"
#include "table/get_context.h"
@@ -37,102 +33,6 @@ Status AppendBlobRefreshRetryFailure(const Status& stale_status,
stale_status, "; refresh retry failed: ", retry_status.ToString());
}
// Runs read(reader) against the blob-file reader for `file_number`. On a
// Corruption -- which a stale cached reader (e.g. the physical file was
// replaced) can surface -- evicts the cached reader, reopens it uncached,
// retries read() once, and refreshes the cache on success. Shared by the
// single-blob read paths (GetBlob / GetBlobRange) so the stale-file recovery
// lives in one place.
//
// `read` is invoked with a BlobFileReader* and returns a Status; because it may
// run twice, it must re-initialize any output it populates on each call (and is
// taken by const reference rather than forwarded, since it is not moved-from).
template <typename ReadFn>
Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
const ReadOptions& read_options,
uint64_t file_number, const ReadFn& read) {
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache->GetBlobFileReader(read_options, file_number,
&blob_file_reader);
if (!s.ok()) {
return s;
}
assert(blob_file_reader.GetValue());
s = read(blob_file_reader.GetValue());
if (!s.IsCorruption()) {
return s;
}
const Status stale_status = s;
blob_file_reader.Reset();
blob_file_cache->Evict(file_number);
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache->OpenBlobFileReaderUncached(
read_options, file_number, &fresh_reader,
/*allow_footer_skip_retry=*/false);
if (!s.ok()) {
return AppendBlobRefreshRetryFailure(stale_status, s);
}
s = read(fresh_reader.get());
if (!s.ok()) {
return AppendBlobRefreshRetryFailure(stale_status, s);
}
CacheHandleGuard<BlobFileReader> ignored_reader;
blob_file_cache
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
.PermitUncheckedError();
return s;
}
// Records lazy wide-column read metrics for one actual storage read issued
// while resolving a lazy result (attributed via Env::IOActivity::kLazyResolve):
// `bytes_read` bytes were read from storage. Covers both whole-column and
// partial reads; not called for cache hits (no storage read).
void RecordLazyRead(Statistics* statistics, uint64_t bytes_read) {
RecordTick(statistics, BLOB_DB_LAZY_READ_COUNT);
RecordTick(statistics, BLOB_DB_LAZY_READ_BYTES, bytes_read);
}
// Additionally records the partial-read metrics for one actual partial
// (byte-range) read: `bytes_read` bytes were fetched instead of the column's
// full `value_size`. Call alongside RecordLazyRead (a partial read is also a
// lazy read).
void RecordLazyPartialRead(Statistics* statistics, uint64_t bytes_read,
uint64_t value_size) {
RecordTick(statistics, BLOB_DB_LAZY_PARTIAL_READ_COUNT);
if (value_size > bytes_read) {
RecordTick(statistics, BLOB_DB_LAZY_PARTIAL_BYTES_SAVED,
value_size - bytes_read);
}
}
// Pins the sub-range [range_offset, range_offset + range_length) of a cache-hit
// whole blob value into *value (clamped: an offset at/past the end yields
// empty, length is clamped to the remainder), keeping the cached bytes alive by
// transferring the cache handle into *value. Shared by the range-read cache-hit
// paths (GetBlobRange / GetSimpleGen2BlobRange).
void PinCacheHitSubRange(CacheHandleGuard<BlobContents>* blob_handle,
uint64_t range_offset, size_t range_length,
PinnableSlice* value) {
const Slice full = blob_handle->GetValue()->data();
Slice sub;
if (range_offset < full.size()) {
const size_t off = static_cast<size_t>(range_offset);
const size_t avail = full.size() - off;
const size_t len = range_length > avail ? avail : range_length;
sub = Slice(full.data() + off, len);
} // else: offset at/past end -> empty (not an error)
value->Reset();
constexpr Cleanable* cleanable = nullptr;
value->PinSlice(sub, cleanable);
blob_handle->TransferTo(value);
}
} // namespace
BlobSource::BlobSource(const ImmutableOptions& immutable_options,
@@ -174,7 +74,6 @@ Status BlobSource::GetBlobFromCache(
assert(cached_blob->GetValue());
PERF_COUNTER_ADD(blob_cache_hit_count, 1);
PERF_COUNTER_ADD(blob_cache_read_byte, cached_blob->GetValue()->size());
RecordTick(statistics_, BLOB_DB_CACHE_HIT);
RecordTick(statistics_, BLOB_DB_CACHE_BYTES_READ,
cached_blob->GetValue()->size());
@@ -325,37 +224,66 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
std::unique_ptr<BlobContents> blob_contents;
{
CacheHandleGuard<BlobFileReader> blob_file_reader;
s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
&blob_file_reader);
if (!s.ok()) {
return s;
}
assert(blob_file_reader.GetValue());
if (compression_type != blob_file_reader.GetValue()->GetCompressionType()) {
return Status::Corruption("Compression type mismatch when reading blob");
}
MemoryAllocator* const allocator =
(blob_cache_ && read_options.fill_cache)
? blob_cache_.get()->memory_allocator()
: nullptr;
uint64_t read_size = 0;
s = ReadBlobWithReaderRetry(
blob_file_cache_, read_options, file_number,
[&](BlobFileReader* reader) {
if (compression_type != reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
}
blob_contents.reset();
read_size = 0;
return reader->GetBlob(read_options, user_key, offset, value_size,
compression_type, prefetch_buffer, allocator,
&blob_contents, &read_size);
});
s = blob_file_reader.GetValue()->GetBlob(
read_options, user_key, offset, value_size, compression_type,
prefetch_buffer, allocator, &blob_contents, &read_size);
if (s.IsCorruption()) {
const Status stale_status = s;
blob_file_reader.Reset();
blob_file_cache_->Evict(file_number);
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache_->OpenBlobFileReaderUncached(
read_options, file_number, &fresh_reader,
/*allow_footer_skip_retry=*/false);
if (!s.ok()) {
return AppendBlobRefreshRetryFailure(stale_status, s);
}
if (compression_type != fresh_reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
}
blob_contents.reset();
read_size = 0;
s = fresh_reader->GetBlob(read_options, user_key, offset, value_size,
compression_type, prefetch_buffer, allocator,
&blob_contents, &read_size);
if (!s.ok()) {
s = AppendBlobRefreshRetryFailure(stale_status, s);
} else {
CacheHandleGuard<BlobFileReader> ignored_reader;
blob_file_cache_
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
.PermitUncheckedError();
}
}
if (!s.ok()) {
return s;
}
if (bytes_read) {
*bytes_read = read_size;
}
// Whole-column read on the lazy resolve path (partial reads go through
// GetBlobRange). Counts the storage read; partial reads are not counted
// here.
if (read_options.io_activity == Env::IOActivity::kLazyResolve) {
RecordLazyRead(statistics_, read_size);
}
}
if (blob_cache_ && read_options.fill_cache) {
@@ -376,449 +304,6 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
return s;
}
Status BlobSource::GetBlobRange(const ReadOptions& read_options,
const Slice& user_key, uint64_t file_number,
uint64_t offset, uint64_t file_size,
uint64_t value_size,
CompressionType compression_type,
uint64_t range_offset, size_t range_length,
PinnableSlice* value, uint64_t* bytes_read) {
assert(value);
// Partial reads are for uncompressed blobs only; the caller decides this (a
// compressed column takes the whole-value GetBlob path and slices).
assert(compression_type == kNoCompression);
// Range reads are (currently) only issued while resolving a lazy result, so
// the lazy read stats below are recorded unconditionally (unlike GetBlob,
// which gates on the activity). Enforce that invariant here.
assert(read_options.io_activity == Env::IOActivity::kLazyResolve);
Status s;
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
CacheHandleGuard<BlobContents> blob_handle;
// First, probe the blob cache for the whole value. On a hit, slice the
// requested sub-range out of the cached value while pinning the cache handle
// (zero-copy, no disk read). A partial read never inserts into the cache.
if (blob_cache_) {
Slice key = cache_key.AsSlice();
s = GetBlobFromCache(key, &blob_handle);
if (s.ok()) {
PinCacheHitSubRange(&blob_handle, range_offset, range_length, value);
if (bytes_read) {
*bytes_read = 0; // served from cache; no disk read
}
return s;
}
}
assert(blob_handle.IsEmpty());
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
return s;
}
// Cache miss: read only the requested sub-range from the file. The result is
// never inserted into the blob cache (see the header comment).
std::unique_ptr<BlobContents> blob_contents;
{
// No cache-fill allocator: a partial value is never inserted into the
// cache.
constexpr MemoryAllocator* allocator = nullptr;
uint64_t read_size = 0;
// Reuses the stale-reader retry shared with GetBlob. A range read skips
// whole-record checksum verification, so the retry handles the stale-file
// case (offset/size mismatch surfacing as Corruption), not a payload
// checksum failure.
s = ReadBlobWithReaderRetry(
blob_file_cache_, read_options, file_number,
[&](BlobFileReader* reader) {
if (compression_type != reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
}
blob_contents.reset();
read_size = 0;
return reader->GetBlobRange(read_options, user_key, offset,
value_size, range_offset, range_length,
allocator, &blob_contents, &read_size);
});
if (!s.ok()) {
return s;
}
if (bytes_read) {
*bytes_read = read_size;
}
RecordLazyRead(statistics_, read_size);
RecordLazyPartialRead(statistics_, read_size, value_size);
}
PinOwnedBlob(&blob_contents, value);
assert(s.ok());
return s;
}
Status BlobSource::GetSimpleGen2Blob(
const ReadOptions& read_options, const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, uint64_t record_offset, uint64_t payload_size,
ChecksumType checksum_type, uint32_t base_context_checksum,
CompressionType expected_compression, PinnableSlice* value,
uint64_t* bytes_read) {
assert(value);
assert(file);
const uint64_t record_size = payload_size + kSimpleGen2BlobTrailerSize;
// The cache key is derived from the SimpleGen2Blob format (shared scheme with
// block-based SST blocks); see GetSimpleGen2BlobCacheKey.
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, record_offset);
Status s;
CacheHandleGuard<BlobContents> blob_handle;
// First, try to get the blob from the cache.
if (blob_cache_) {
Slice key = cache_key.AsSlice();
s = GetBlobFromCache(key, &blob_handle);
if (s.ok()) {
PinCachedBlob(&blob_handle, value);
// For consistency, the on-disk record size is assigned to bytes_read on
// both cache hits and misses.
if (bytes_read) {
*bytes_read = record_size;
}
return s;
}
}
assert(blob_handle.IsEmpty());
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
return s;
}
// Cache miss (or no cache configured). Read the record into a buffer
// allocated from the blob cache's memory allocator when we intend to insert
// it, exposing the uncompressed payload as BlobContents (the trailer just
// sits unused at the tail of the buffer).
MemoryAllocator* const allocator = (blob_cache_ && read_options.fill_cache)
? blob_cache_.get()->memory_allocator()
: nullptr;
CacheAllocationPtr buf =
AllocateBlock(static_cast<size_t>(record_size), allocator);
s = ReadAndVerifySimpleGen2BlobRecord(
read_options, file, record_offset, static_cast<size_t>(payload_size),
static_cast<size_t>(record_size), checksum_type, base_context_checksum,
expected_compression, buf.get());
if (!s.ok()) {
return s;
}
std::unique_ptr<BlobContents> blob_contents(
new BlobContents(std::move(buf), static_cast<size_t>(payload_size)));
// Record the per-read statistics (mirrors BlobFileReader::GetBlob).
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, record_size);
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, record_size);
if (bytes_read) {
*bytes_read = record_size;
}
// Whole-column read on the lazy resolve path (partial reads go through
// GetSimpleGen2BlobRange).
if (read_options.io_activity == Env::IOActivity::kLazyResolve) {
RecordLazyRead(statistics_, record_size);
}
if (blob_cache_ && read_options.fill_cache) {
// If filling cache is allowed and a cache is configured, try to put the
// blob into the cache.
Slice key = cache_key.AsSlice();
s = PutBlobIntoCache(key, &blob_contents, &blob_handle);
if (!s.ok()) {
return s;
}
PinCachedBlob(&blob_handle, value);
} else {
PinOwnedBlob(&blob_contents, value);
}
assert(s.ok());
return s;
}
Status BlobSource::GetSimpleGen2BlobRange(
const ReadOptions& read_options, const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, uint64_t record_offset, uint64_t payload_size,
ChecksumType /*checksum_type*/, uint32_t /*base_context_checksum*/,
CompressionType expected_compression, uint64_t range_offset,
size_t range_length, PinnableSlice* value, uint64_t* bytes_read) {
assert(value);
assert(file);
// Partial reads are for uncompressed payloads only; the caller decides this
// (a compressed column takes the whole-payload GetSimpleGen2Blob path +
// slice).
assert(expected_compression == kNoCompression);
// Range reads are (currently) only issued while resolving a lazy result, so
// the lazy read stats below are recorded unconditionally (unlike
// GetSimpleGen2Blob, which gates on the activity). Enforce that invariant
// here.
assert(read_options.io_activity == Env::IOActivity::kLazyResolve);
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, record_offset);
Status s;
CacheHandleGuard<BlobContents> blob_handle;
// First, probe the blob cache for the whole payload. On a hit, slice the
// requested sub-range out of the cached payload while pinning the cache
// handle (zero-copy, no disk read). A partial read never inserts into the
// cache.
if (blob_cache_) {
Slice key = cache_key.AsSlice();
s = GetBlobFromCache(key, &blob_handle);
if (s.ok()) {
PinCacheHitSubRange(&blob_handle, range_offset, range_length, value);
if (bytes_read) {
*bytes_read = 0; // served from cache; no disk read
}
return s;
}
}
assert(blob_handle.IsEmpty());
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
return s;
}
// Cache miss: read only the requested sub-range from the file. The result is
// never inserted into the blob cache (a partial payload cannot represent the
// whole-record cache entry).
CacheAllocationPtr buf = AllocateBlock(range_length, /*allocator=*/nullptr);
s = ReadSimpleGen2BlobRange(read_options, file, record_offset,
static_cast<size_t>(payload_size), range_offset,
range_length, expected_compression, buf.get());
if (!s.ok()) {
return s;
}
std::unique_ptr<BlobContents> blob_contents(
new BlobContents(std::move(buf), range_length));
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, range_length);
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, range_length);
RecordLazyRead(statistics_, range_length);
RecordLazyPartialRead(statistics_, range_length, payload_size);
if (bytes_read) {
*bytes_read = range_length;
}
PinOwnedBlob(&blob_contents, value);
assert(s.ok());
return s;
}
void BlobSource::MultiGetSimpleGen2Blob(
const ReadOptions& read_options, const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, ChecksumType checksum_type,
uint32_t base_context_checksum, size_t num_records,
SimpleGen2BlobReadRequest* reqs) {
assert(file);
if (num_records == 0) {
return;
}
const bool no_io = read_options.read_tier == kBlockCacheTier;
MemoryAllocator* const allocator = (blob_cache_ && read_options.fill_cache)
? blob_cache_.get()->memory_allocator()
: nullptr;
// Records that missed the cache and must be read from disk, in read order;
// each owns its buffer until we build a BlobContents from it below.
struct Pending {
size_t idx;
CacheAllocationPtr buf;
uint64_t record_size;
};
std::vector<Pending> pending;
std::vector<SimpleGen2RecordReadRequest> read_reqs;
for (size_t i = 0; i < num_records; ++i) {
SimpleGen2BlobReadRequest& req = reqs[i];
assert(req.result);
assert(req.status);
const uint64_t record_size = req.payload_size + kSimpleGen2BlobTrailerSize;
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, req.record_offset);
if (blob_cache_) {
CacheHandleGuard<BlobContents> blob_handle;
const Status cs = GetBlobFromCache(cache_key.AsSlice(), &blob_handle);
if (cs.ok()) {
*req.status = cs;
PinCachedBlob(&blob_handle, req.result);
continue;
}
}
if (no_io) {
*req.status =
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
continue;
}
CacheAllocationPtr buf =
AllocateBlock(static_cast<size_t>(record_size), allocator);
read_reqs.push_back(SimpleGen2RecordReadRequest{
req.record_offset, static_cast<size_t>(req.payload_size),
static_cast<size_t>(record_size), req.expected_compression, buf.get(),
req.status});
pending.push_back(Pending{i, std::move(buf), record_size});
}
if (read_reqs.empty()) {
return;
}
ReadAndVerifySimpleGen2BlobRecords(read_options, file, checksum_type,
base_context_checksum, read_reqs.size(),
read_reqs.data());
for (size_t k = 0; k < pending.size(); ++k) {
SimpleGen2BlobReadRequest& req = reqs[pending[k].idx];
if (!req.status->ok()) {
continue;
}
const uint64_t record_size = pending[k].record_size;
std::unique_ptr<BlobContents> blob_contents(new BlobContents(
std::move(pending[k].buf), static_cast<size_t>(req.payload_size)));
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, record_size);
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, record_size);
if (read_options.io_activity == Env::IOActivity::kLazyResolve) {
RecordLazyRead(statistics_, record_size);
}
if (blob_cache_ && read_options.fill_cache) {
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, req.record_offset);
CacheHandleGuard<BlobContents> blob_handle;
const Status ps =
PutBlobIntoCache(cache_key.AsSlice(), &blob_contents, &blob_handle);
if (!ps.ok()) {
*req.status = ps;
} else {
PinCachedBlob(&blob_handle, req.result);
}
} else {
PinOwnedBlob(&blob_contents, req.result);
}
}
}
void BlobSource::MultiGetSimpleGen2BlobRange(
const ReadOptions& read_options, const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, size_t num_records,
SimpleGen2BlobRangeReadRequest* reqs) {
assert(file);
// Range reads are only issued while resolving a lazy result (partial reads
// are lazy-only), so the lazy read stats below are recorded unconditionally.
assert(read_options.io_activity == Env::IOActivity::kLazyResolve);
if (num_records == 0) {
return;
}
const bool no_io = read_options.read_tier == kBlockCacheTier;
struct Pending {
size_t idx;
CacheAllocationPtr buf;
};
std::vector<Pending> pending;
std::vector<SimpleGen2RangeReadRequest> read_reqs;
for (size_t i = 0; i < num_records; ++i) {
SimpleGen2BlobRangeReadRequest& req = reqs[i];
assert(req.result);
assert(req.status);
const CacheKey cache_key =
GetSimpleGen2BlobCacheKey(base_cache_key, req.record_offset);
// Probe the whole-payload cache; on a hit slice the requested sub-range.
if (blob_cache_) {
CacheHandleGuard<BlobContents> blob_handle;
const Status cs = GetBlobFromCache(cache_key.AsSlice(), &blob_handle);
if (cs.ok()) {
*req.status = cs;
PinCacheHitSubRange(&blob_handle, req.range_offset, req.range_length,
req.result);
continue;
}
}
if (no_io) {
*req.status =
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
continue;
}
// No cache-fill allocator: a partial payload is never inserted.
CacheAllocationPtr buf =
AllocateBlock(req.range_length, /*allocator=*/nullptr);
read_reqs.push_back(SimpleGen2RangeReadRequest{
req.record_offset, static_cast<size_t>(req.payload_size),
req.range_offset, req.range_length, req.expected_compression, buf.get(),
req.status});
pending.push_back(Pending{i, std::move(buf)});
}
if (read_reqs.empty()) {
return;
}
ReadSimpleGen2BlobRanges(read_options, file, read_reqs.size(),
read_reqs.data());
for (size_t k = 0; k < pending.size(); ++k) {
SimpleGen2BlobRangeReadRequest& req = reqs[pending[k].idx];
if (!req.status->ok()) {
continue;
}
std::unique_ptr<BlobContents> blob_contents(
new BlobContents(std::move(pending[k].buf), req.range_length));
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, req.range_length);
PERF_COUNTER_ADD(blob_read_count, 1);
PERF_COUNTER_ADD(blob_read_byte, req.range_length);
RecordLazyRead(statistics_, req.range_length);
RecordLazyPartialRead(statistics_, req.range_length, req.payload_size);
PinOwnedBlob(&blob_contents, req.result);
}
}
void BlobSource::MultiGetBlob(const ReadOptions& read_options,
autovector<BlobFileReadRequests>& blob_reqs,
uint64_t* bytes_read) {
@@ -1070,24 +555,6 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
}
}
// Whole-column reads on the lazy resolve path count as lazy reads (mirrors
// BlobSource::GetBlob, which records only on a disk read, not a cache hit).
// Gated on the activity so regular MultiGet is unaffected; the misses read
// above are exactly the disk reads for these blob columns.
if (read_options.io_activity == Env::IOActivity::kLazyResolve) {
for (auto& [req, blob_contents] : _blob_reqs) {
assert(req);
if (req->status->ok()) {
const uint64_t adjustment =
read_options.verify_checksums
? BlobLogRecord::CalculateAdjustmentForRecordHeader(
req->user_key->size())
: 0;
RecordLazyRead(statistics_, req->len + adjustment);
}
}
}
total_bytes += _bytes_read;
if (bytes_read) {
*bytes_read = total_bytes;
@@ -1095,138 +562,6 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
}
}
void BlobSource::MultiGetBlobRange(
const ReadOptions& read_options,
autovector<BlobFileRangeReadRequests>& blob_reqs, uint64_t* bytes_read) {
assert(blob_reqs.size() > 0);
uint64_t total_bytes_read = 0;
uint64_t bytes_read_in_file = 0;
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
// Sort by effective read offset so the file system layer can coalesce
// adjacent sub-range reads within one MultiRead.
std::sort(blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
[](const BlobRangeReadRequest& lhs,
const BlobRangeReadRequest& rhs) -> bool {
return lhs.offset + lhs.range_offset <
rhs.offset + rhs.range_offset;
});
MultiGetBlobRangeFromOneFile(read_options, file_number, file_size,
blob_reqs_in_file, &bytes_read_in_file);
total_bytes_read += bytes_read_in_file;
bytes_read_in_file = 0; // reset: some early-return paths do not write it
}
if (bytes_read) {
*bytes_read = total_bytes_read;
}
}
void BlobSource::MultiGetBlobRangeFromOneFile(
const ReadOptions& read_options, uint64_t file_number,
uint64_t /*file_size*/, autovector<BlobRangeReadRequest>& blob_reqs,
uint64_t* bytes_read) {
const size_t num_blobs = blob_reqs.size();
assert(num_blobs > 0);
assert(num_blobs <= MultiGetContext::MAX_BATCH_SIZE);
// Range reads are only issued while resolving a lazy result (partial reads
// are lazy-only), so the lazy read stats below are recorded unconditionally.
assert(read_options.io_activity == Env::IOActivity::kLazyResolve);
using Mask = uint64_t;
Mask cache_hit_mask = 0;
uint64_t total_bytes = 0;
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
// Probe the whole-value cache per request; on a hit, slice the requested
// sub-range (zero-copy, no disk read). A partial read is never cache-filled.
if (blob_cache_) {
size_t cached_blob_count = 0;
for (size_t i = 0; i < num_blobs; ++i) {
BlobRangeReadRequest& req = blob_reqs[i];
assert(req.status);
CacheHandleGuard<BlobContents> blob_handle;
const CacheKey cache_key = base_cache_key.WithOffset(req.offset);
const Status s = GetBlobFromCache(cache_key.AsSlice(), &blob_handle);
if (s.ok()) {
*req.status = s;
PinCacheHitSubRange(&blob_handle, req.range_offset, req.range_length,
req.result);
++cached_blob_count;
cache_hit_mask |= (Mask{1} << i); // hit -> no disk read
}
}
if (cached_blob_count == num_blobs) {
if (bytes_read) {
*bytes_read = 0;
}
return;
}
}
const bool no_io = read_options.read_tier == kBlockCacheTier;
if (no_io) {
for (size_t i = 0; i < num_blobs; ++i) {
if (!(cache_hit_mask & (Mask{1} << i))) {
assert(blob_reqs[i].status);
*blob_reqs[i].status =
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
}
}
return;
}
autovector<std::pair<BlobRangeReadRequest*, std::unique_ptr<BlobContents>>>
_blob_reqs;
for (size_t i = 0; i < num_blobs; ++i) {
if (!(cache_hit_mask & (Mask{1} << i))) {
_blob_reqs.emplace_back(&blob_reqs[i], std::unique_ptr<BlobContents>());
}
}
// Lazy range reads run against a pinned SuperVersion (max_open_files == -1),
// and blob file numbers are never reused, so the stale cached-reader race
// that MultiGetBlobFromOneFile guards against cannot occur here; a Corruption
// is simply surfaced per request. Hence no reader-refresh retry and no
// cache-fill (a partial value cannot represent the whole-record cache entry).
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
&blob_file_reader);
if (!s.ok()) {
for (auto& blob_req : _blob_reqs) {
assert(blob_req.first);
assert(blob_req.first->status);
*blob_req.first->status = s;
}
return;
}
assert(blob_file_reader.GetValue());
uint64_t _bytes_read = 0;
blob_file_reader.GetValue()->MultiGetBlobRange(read_options, _blob_reqs,
&_bytes_read);
for (auto& [req, blob_contents] : _blob_reqs) {
assert(req);
if (req->status->ok()) {
PinOwnedBlob(&blob_contents, req->result);
RecordLazyRead(statistics_, req->range_length);
RecordLazyPartialRead(statistics_, req->range_length, req->value_size);
}
}
total_bytes += _bytes_read;
if (bytes_read) {
*bytes_read = total_bytes;
}
}
bool BlobSource::TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
uint64_t offset, size_t* charge) const {
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
-156
View File
@@ -25,32 +25,6 @@ struct MutableCFOptions;
class Status;
class FilePrefetchBuffer;
class Slice;
class RandomAccessFileReader;
enum ChecksumType : char;
// One whole-record request for BlobSource::MultiGetSimpleGen2Blob (the batched
// counterpart of GetSimpleGen2Blob). The per-file context (base cache key,
// file, checksum context) is passed once to the batch call; these are the
// per-record inputs and outputs.
struct SimpleGen2BlobReadRequest {
uint64_t record_offset = 0;
uint64_t payload_size = 0;
CompressionType expected_compression = kNoCompression;
PinnableSlice* result = nullptr;
Status* status = nullptr;
};
// One sub-range request for BlobSource::MultiGetSimpleGen2BlobRange (the
// batched counterpart of GetSimpleGen2BlobRange).
struct SimpleGen2BlobRangeReadRequest {
uint64_t record_offset = 0;
uint64_t payload_size = 0;
uint64_t range_offset = 0;
size_t range_length = 0;
CompressionType expected_compression = kNoCompression;
PinnableSlice* result = nullptr;
Status* status = nullptr;
};
// BlobSource is a class that provides universal access to blobs, regardless of
// whether they are in the blob cache, secondary cache, or (remote) storage.
@@ -85,118 +59,6 @@ class BlobSource {
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
uint64_t* bytes_read);
// Reads a byte sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* blob's value, reading only those bytes on a cache miss.
//
// First probes the blob cache for the whole value. On a hit, pins the cache
// handle into *value and points it at the requested sub-range (zero-copy, no
// disk I/O). On a miss, reads only the requested bytes from the blob file
// (via BlobFileReader::GetBlobRange -- no record-header/key read and no
// whole-record checksum verification) and pins the owned buffer into *value.
//
// Unlike GetBlob, a partial read never inserts into the blob cache: the cache
// entry is keyed per (file, offset) and holds the *whole* BlobContents, so a
// partial value would violate that invariant. `compression_type` must be
// kNoCompression (a strict sub-range of a compressed blob cannot be
// decompressed in isolation); callers that need a compressed column, a
// whole-column read, or checksum verification use GetBlob and slice instead.
//
// The caller must ensure range_offset + range_length <= value_size. On a miss
// *bytes_read (when non-null) is the number of bytes read from the file; on a
// hit it is 0.
Status GetBlobRange(const ReadOptions& read_options, const Slice& user_key,
uint64_t file_number, uint64_t offset, uint64_t file_size,
uint64_t value_size, CompressionType compression_type,
uint64_t range_offset, size_t range_length,
PinnableSlice* value, uint64_t* bytes_read);
// Reads a SimpleGen2Blob payload (see db/blob/blob_gen2_format.h) through the
// blob value cache and BLOB_DB_* statistics. This is the counterpart to
// GetBlob() for the second-generation blob record format, which is read
// directly from a RandomAccessFileReader rather than from a traditional blob
// file.
//
// The cache key is derived from the SimpleGen2Blob format itself, not chosen
// by the caller: it is GetSimpleGen2BlobCacheKey(base_cache_key,
// record_offset), the same offset scheme block-based SST blocks use. The
// caller supplies only its file's `base_cache_key` (db_id / db_session_id /
// file_number). This keeps blob records collision-free with the file's data
// blocks even when the blob cache and block cache are the same cache.
//
// `file`, `record_offset`, `payload_size`, `checksum_type`,
// `base_context_checksum`, and `expected_compression` are the inputs to the
// SimpleGen2Blob reader used on a cache miss (see
// ReadAndVerifySimpleGen2BlobRecord). The on-disk record size (payload +
// trailer) is reported via `*bytes_read` (when non-null) and the
// BLOB_DB_BLOB_FILE_BYTES_READ / blob_read_byte counters, consistently on
// both cache hits and misses.
//
// On a cache hit, pins the cached value into `*value` (no copy). On a miss,
// reads + verifies the record into a cache-allocator buffer, records the
// per-read stats, inserts it into the cache (when configured and fill_cache
// is set), and pins it into `*value`. If blob_cache_ is not configured, the
// record is still read and read stats recorded, just without a cache
// lookup/insert.
Status GetSimpleGen2Blob(const ReadOptions& read_options,
const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file, uint64_t record_offset,
uint64_t payload_size, ChecksumType checksum_type,
uint32_t base_context_checksum,
CompressionType expected_compression,
PinnableSlice* value, uint64_t* bytes_read);
// Reads a byte sub-range [range_offset, range_offset + range_length) of an
// *uncompressed* SimpleGen2Blob payload, reading only those bytes on a cache
// miss. This is the embedded (same-file) counterpart of GetBlobRange().
//
// First probes the blob cache for the whole payload (keyed exactly as
// GetSimpleGen2Blob). On a hit, pins the cache handle into *value and points
// it at the requested sub-range (zero-copy, no disk I/O). On a miss, reads
// only the requested bytes from the file (via ReadSimpleGen2BlobRange -- no
// trailer read and no checksum verification) and pins the owned buffer.
//
// Like GetBlobRange, a partial read never inserts into the blob cache (the
// cache entry holds the whole payload). `expected_compression` must be
// kNoCompression. The caller must ensure range_offset + range_length <=
// payload_size. On a miss *bytes_read (when non-null) is the number of bytes
// read from the file; on a hit it is 0.
Status GetSimpleGen2BlobRange(const ReadOptions& read_options,
const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file,
uint64_t record_offset, uint64_t payload_size,
ChecksumType checksum_type,
uint32_t base_context_checksum,
CompressionType expected_compression,
uint64_t range_offset, size_t range_length,
PinnableSlice* value, uint64_t* bytes_read);
// Batched counterpart of GetSimpleGen2Blob: resolves `num_records` whole
// embedded (SimpleGen2Blob) records from one file, coalescing all cache-miss
// reads into a single MultiRead. The per-file context (base cache key, file,
// checksum type, base context checksum) is shared by all records; per-record
// inputs/outputs are in `reqs`. Each record independently probes the blob
// cache and, on a miss, is read + verified from the file; whole records are
// cache-filled (when configured and fill_cache) exactly as GetSimpleGen2Blob.
// Per-record outcome is written to *reqs[i].status.
void MultiGetSimpleGen2Blob(const ReadOptions& read_options,
const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file,
ChecksumType checksum_type,
uint32_t base_context_checksum,
size_t num_records,
SimpleGen2BlobReadRequest* reqs);
// Batched counterpart of GetSimpleGen2BlobRange: resolves `num_records`
// uncompressed embedded sub-ranges from one file, coalescing cache-miss reads
// into a single MultiRead. Like GetSimpleGen2BlobRange, each record probes
// the whole-payload cache (slicing on a hit) and a partial read is never
// inserted into the cache. Per-record outcome is in *reqs[i].status.
void MultiGetSimpleGen2BlobRange(const ReadOptions& read_options,
const OffsetableCacheKey& base_cache_key,
RandomAccessFileReader* file,
size_t num_records,
SimpleGen2BlobRangeReadRequest* reqs);
// Read multiple blobs from the underlying cache or blob file(s).
//
// If successful, returns ok and sets "result" in the elements of "blob_reqs"
@@ -236,24 +98,6 @@ class BlobSource {
autovector<BlobReadRequest>& blob_reqs,
uint64_t* bytes_read);
// Byte-range (partial) multi-read counterpart of MultiGetBlob: for each
// request reads only its sub-range of an *uncompressed* blob value, from the
// blob cache (whole-value hit, sliced) or coalesced disk reads (one MultiRead
// per file). Like GetBlobRange, a partial read never fills the blob cache.
// Groups requests by file and dispatches each file via
// MultiGetBlobRangeFromOneFile. Per-request outcome is in
// *blob_reqs[..].status.
void MultiGetBlobRange(const ReadOptions& read_options,
autovector<BlobFileRangeReadRequests>& blob_reqs,
uint64_t* bytes_read);
// Byte-range (partial) multi-read counterpart of MultiGetBlobFromOneFile for
// a single blob file. See MultiGetBlobRange.
void MultiGetBlobRangeFromOneFile(const ReadOptions& read_options,
uint64_t file_number, uint64_t file_size,
autovector<BlobRangeReadRequest>& blob_reqs,
uint64_t* bytes_read);
inline Status GetBlobFileReader(
const ReadOptions& read_options, uint64_t blob_file_number,
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
-10
View File
@@ -229,7 +229,6 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
// Retrieved the blob cache num_blobs * 3 times via TEST_BlobInCache,
// GetBlob, and TEST_BlobInCache.
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count, 0);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte, 0);
ASSERT_EQ((int)get_perf_context()->blob_read_count, num_blobs);
ASSERT_EQ((int)get_perf_context()->blob_read_byte, total_bytes);
ASSERT_GE((int)get_perf_context()->blob_checksum_time, 0);
@@ -263,8 +262,6 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
blob_bytes += blob_sizes[i];
total_bytes += bytes_read;
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count, i);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte,
blob_bytes - blob_sizes[i]);
ASSERT_EQ((int)get_perf_context()->blob_read_count, i + 1);
ASSERT_EQ((int)get_perf_context()->blob_read_byte, total_bytes);
@@ -272,13 +269,11 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
blob_offsets[i]));
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count, i + 1);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte, blob_bytes);
ASSERT_EQ((int)get_perf_context()->blob_read_count, i + 1);
ASSERT_EQ((int)get_perf_context()->blob_read_byte, total_bytes);
}
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count, num_blobs);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte, blob_bytes);
ASSERT_EQ((int)get_perf_context()->blob_read_count, num_blobs);
ASSERT_EQ((int)get_perf_context()->blob_read_byte, total_bytes);
@@ -317,7 +312,6 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
// Retrieved the blob cache num_blobs * 3 times via TEST_BlobInCache,
// GetBlob, and TEST_BlobInCache.
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count, num_blobs * 3);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte, blob_bytes * 3);
ASSERT_EQ((int)get_perf_context()->blob_read_count, 0); // without i/o
ASSERT_EQ((int)get_perf_context()->blob_read_byte, 0); // without i/o
@@ -696,8 +690,6 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
// TEST_BlobInCache.
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count,
num_blobs * blob_files);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte,
blob_value_bytes * blob_files);
ASSERT_EQ((int)get_perf_context()->blob_read_count,
num_blobs * blob_files); // blocking i/o
ASSERT_EQ((int)get_perf_context()->blob_read_byte,
@@ -758,8 +750,6 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
// via MultiGetBlob and TEST_BlobInCache.
ASSERT_EQ((int)get_perf_context()->blob_cache_hit_count,
num_blobs * blob_files * 2);
ASSERT_EQ((int)get_perf_context()->blob_cache_read_byte,
blob_value_bytes * blob_files * 2);
ASSERT_EQ((int)get_perf_context()->blob_read_count,
0); // blocking i/o
ASSERT_EQ((int)get_perf_context()->blob_read_byte,
+3 -2
View File
@@ -215,10 +215,11 @@ Status BlobWriteBatchTransformer::PutEntityCF(uint32_t column_family_id,
output_batch_, column_family_id, key, entity);
}
Slice entity_ref = entity;
std::vector<WideColumn> columns;
std::vector<std::pair<size_t, BlobIndex>> existing_blob_columns;
Status status = WideColumnSerialization::Deserialize(entity, columns,
&existing_blob_columns);
Status status = WideColumnSerialization::DeserializeV2(entity_ref, columns,
existing_blob_columns);
if (status.ok()) {
// Reject pre-serialized entities that already contain blob references.
// Passing them through would preserve references to blob files outside this
-71
View File
@@ -5,8 +5,6 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <set>
#include <sstream>
#include <string>
@@ -65,75 +63,6 @@ TEST_F(DBBlobBasicTest, GetBlob) {
.IsIncomplete());
}
TEST_F(DBBlobBasicTest, BlobFileWritableFileMaxBufferSize) {
constexpr uint64_t kBlobWriterBufferSize = 128 * 1024;
Options options = GetDefaultOptions();
options.enable_blob_files = true;
options.min_blob_size = 0;
options.writable_file_max_buffer_size = 1024 * 1024;
options.blob_file_writable_file_max_buffer_size = kBlobWriterBufferSize;
Reopen(options);
std::atomic<int> matching_blob_writer_count{0};
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->SetCallBack(
"WritableFileWriter::WritableFileWriter:0", [&](void* arg) {
const uint64_t max_buffer_size =
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(arg));
if (max_buffer_size == kBlobWriterBufferSize) {
++matching_blob_writer_count;
}
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(Put("key", std::string(1024, 'v')));
ASSERT_OK(Flush());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_GE(matching_blob_writer_count.load(), 1);
}
TEST_F(DBBlobBasicTest,
DirectWriteBlobFileWritableFileMaxBufferSizeSetOptions) {
constexpr uint64_t kBlobWriterBufferSize = 64 * 1024;
Options options = GetDefaultOptions();
options.enable_blob_files = true;
options.enable_blob_direct_write = true;
options.allow_concurrent_memtable_write = false;
options.min_blob_size = 0;
options.blob_file_size = 200;
options.writable_file_max_buffer_size = 1024 * 1024;
Reopen(options);
std::atomic<int> matching_blob_writer_count{0};
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->SetCallBack(
"WritableFileWriter::WritableFileWriter:0", [&](void* arg) {
const uint64_t max_buffer_size =
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(arg));
if (max_buffer_size == kBlobWriterBufferSize) {
++matching_blob_writer_count;
}
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(Put("first", std::string(300, 'a')));
ASSERT_OK(db_->SetOptions({{"blob_file_writable_file_max_buffer_size",
std::to_string(kBlobWriterBufferSize)}}));
ASSERT_OK(Put("second", std::string(300, 'b')));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_GE(matching_blob_writer_count.load(), 1);
}
TEST_F(DBBlobBasicTest, EmptyValueNotStoredAsBlob) {
// Regression test for crash when empty blob value is evicted to
// CompressedSecondaryCache (T261142690). Empty values should always be
-229
View File
@@ -10,14 +10,11 @@
#include <algorithm>
#include <array>
#include <functional>
#include <limits>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "cache/compressed_secondary_cache.h"
@@ -29,12 +26,10 @@
#include "db/db_test_util.h"
#include "db/db_with_timestamp_test_util.h"
#include "db/wide/wide_column_test_util.h"
#include "env/composite_env_wrapper.h"
#include "file/filename.h"
#include "file/random_access_file_reader.h"
#include "port/stack_trace.h"
#include "rocksdb/convenience.h"
#include "rocksdb/file_system.h"
#include "rocksdb/trace_reader_writer.h"
#include "rocksdb/trace_record.h"
#include "rocksdb/utilities/replayer.h"
@@ -106,206 +101,6 @@ class RecordingBlobDirectWritePartitionStrategy
std::vector<Call> calls_;
};
namespace {
// Matches BlobDB file names so the test filesystem can special-case only active
// blob files without changing unrelated file opens.
bool IsBlobFilePath(const std::string& fname) {
const size_t basename_pos = fname.find_last_of("/\\");
const std::string basename = basename_pos == std::string::npos
? fname
: fname.substr(basename_pos + 1);
uint64_t file_number = 0;
FileType file_type = kWalFile;
return ParseFileName(basename, &file_number, &file_type) &&
file_type == kBlobFile;
}
// Keeps track of when an active blob file stops being writer-visible so the
// test filesystem can model current-size visibility only while the file remains
// active.
class ActiveBlobVisibilityWritableFile : public FSWritableFileOwnerWrapper {
public:
ActiveBlobVisibilityWritableFile(std::unique_ptr<FSWritableFile>&& target,
std::function<void()> on_close)
: FSWritableFileOwnerWrapper(std::move(target)),
on_close_(std::move(on_close)) {}
~ActiveBlobVisibilityWritableFile() override { DeactivateOnce(); }
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
IOStatus s = target()->Close(options, dbg);
DeactivateOnce();
return s;
}
private:
void DeactivateOnce() {
if (on_close_) {
on_close_();
on_close_ = nullptr;
}
}
std::function<void()> on_close_;
};
// Simulates a remote readable file whose GetFileSize() either exposes the live
// on-disk size for default-contract active blobs or hides it for
// append-only-no-readers files.
class ActiveBlobVisibilityRandomAccessFile
: public FSRandomAccessFileOwnerWrapper {
public:
ActiveBlobVisibilityRandomAccessFile(
std::unique_ptr<FSRandomAccessFile>&& target, bool expose_current_size,
std::shared_ptr<FileSystem> underlying_fs, std::string fname)
: FSRandomAccessFileOwnerWrapper(std::move(target)),
expose_current_size_(expose_current_size),
underlying_fs_(std::move(underlying_fs)),
fname_(std::move(fname)) {}
IOStatus GetFileSize(uint64_t* result) override {
if (!expose_current_size_) {
*result = 0;
return IOStatus::OK();
}
// Delegate to the underlying FS path-level GetFileSize (bypassing the
// wrapper that reports 0 for active blobs) to simulate a remote FS that
// exposes the current file size through the open handle.
return underlying_fs_->GetFileSize(fname_, IOOptions(), result,
/*dbg=*/nullptr);
}
private:
const bool expose_current_size_;
std::shared_ptr<FileSystem> underlying_fs_;
std::string fname_;
};
// Models a remote filesystem where active direct-write blobs are only
// reader-visible when the writer does not promise no concurrent readers.
class RemoteBlobVisibilityFileSystem : public FileSystemWrapper {
public:
explicit RemoteBlobVisibilityFileSystem(const std::shared_ptr<FileSystem>& fs)
: FileSystemWrapper(fs) {}
static const char* kClassName() { return "RemoteBlobVisibilityFileSystem"; }
const char* Name() const override { return kClassName(); }
IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts,
std::unique_ptr<FSWritableFile>* result,
IODebugContext* dbg) override {
std::unique_ptr<FSWritableFile> file;
IOStatus s = target()->NewWritableFile(fname, opts, &file, dbg);
if (!s.ok() || !IsBlobFilePath(fname)) {
*result = std::move(file);
return IOStatus(std::move(s));
}
const bool has_no_reopen_for_write_contract = HasFileOpenContract(
opts.open_contract, FileOpenContract::kNoReopenForWrite);
const bool has_no_readers_while_open_for_write_contract =
HasFileOpenContract(opts.open_contract,
FileOpenContract::kNoReadersWhileOpenForWrite);
{
std::lock_guard<std::mutex> lock(mu_);
saw_no_reopen_for_write_writer_contract_ |=
has_no_reopen_for_write_contract;
saw_no_readers_while_open_for_write_writer_contract_ |=
has_no_readers_while_open_for_write_contract;
if (!has_no_readers_while_open_for_write_contract) {
active_blob_paths_.insert(fname);
}
}
if (has_no_readers_while_open_for_write_contract) {
*result = std::move(file);
return IOStatus::OK();
}
result->reset(new ActiveBlobVisibilityWritableFile(
std::move(file), [this, tracked_path = std::string(fname)]() {
DeactivateBlobPath(tracked_path);
}));
return IOStatus::OK();
}
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
std::unique_ptr<FSRandomAccessFile> file;
IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg);
if (!s.ok() || !IsBlobFilePath(fname)) {
*result = std::move(file);
return IOStatus(std::move(s));
}
const bool has_no_readers_while_open_for_write_contract =
HasFileOpenContract(opts.open_contract,
FileOpenContract::kNoReadersWhileOpenForWrite);
const bool active_blob = IsActiveBlobPath(fname);
{
std::lock_guard<std::mutex> lock(mu_);
saw_no_readers_while_open_for_write_reader_contract_ |=
has_no_readers_while_open_for_write_contract;
}
if (!active_blob) {
*result = std::move(file);
return IOStatus::OK();
}
result->reset(new ActiveBlobVisibilityRandomAccessFile(
std::move(file), !has_no_readers_while_open_for_write_contract, target_,
fname));
return IOStatus::OK();
}
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (IsActiveBlobPath(fname)) {
*file_size = 0;
return IOStatus::OK();
}
return target()->GetFileSize(fname, options, file_size, dbg);
}
bool SawNoReopenForWriteWriterContract() const {
std::lock_guard<std::mutex> lock(mu_);
return saw_no_reopen_for_write_writer_contract_;
}
bool SawNoReadersWhileOpenForWriteWriterContract() const {
std::lock_guard<std::mutex> lock(mu_);
return saw_no_readers_while_open_for_write_writer_contract_;
}
bool SawNoReadersWhileOpenForWriteReaderContract() const {
std::lock_guard<std::mutex> lock(mu_);
return saw_no_readers_while_open_for_write_reader_contract_;
}
private:
void DeactivateBlobPath(const std::string& fname) {
std::lock_guard<std::mutex> lock(mu_);
active_blob_paths_.erase(fname);
}
bool IsActiveBlobPath(const std::string& fname) const {
std::lock_guard<std::mutex> lock(mu_);
return active_blob_paths_.find(fname) != active_blob_paths_.end();
}
mutable std::mutex mu_;
bool saw_no_reopen_for_write_writer_contract_ = false;
bool saw_no_readers_while_open_for_write_writer_contract_ = false;
bool saw_no_readers_while_open_for_write_reader_contract_ = false;
std::unordered_set<std::string> active_blob_paths_;
};
} // namespace
class DBBlobDirectWriteTest : public DBTestBase {
protected:
DBBlobDirectWriteTest()
@@ -1021,30 +816,6 @@ TEST_F(DBBlobDirectWriteTest, DirectWriteImmutableMemtableRead) {
verify_reads();
}
// Verifies active blob direct-write files promise no write reopen without also
// promising no concurrent readers.
TEST_F(DBBlobDirectWriteTest, DirectWriteUsesReadableContractForRemoteFile) {
auto remote_fs =
std::make_shared<RemoteBlobVisibilityFileSystem>(env_->GetFileSystem());
std::unique_ptr<Env> remote_env(new CompositeEnvWrapper(env_, remote_fs));
Options options = GetDirectWriteOptions();
options.env = remote_env.get();
Reopen(options);
const std::string key = "remote_blob_key";
const std::string value(128, 'w');
ASSERT_OK(Put(key, value));
ASSERT_TRUE(remote_fs->SawNoReopenForWriteWriterContract());
ASSERT_FALSE(remote_fs->SawNoReadersWhileOpenForWriteWriterContract());
ASSERT_EQ(Get(key), value);
ASSERT_FALSE(remote_fs->SawNoReadersWhileOpenForWriteReaderContract());
Close();
}
TEST_F(DBBlobDirectWriteTest, DirectWriteRefreshesReaderAfterFlush) {
Options options = GetDirectWriteOptions();
options.blob_direct_write_partitions = 1;
+14 -803
View File
@@ -15,7 +15,6 @@
#include <vector>
#include "db/arena_wrapped_db_iter.h"
#include "db/blob/blob_fetcher.h"
#include "db/blob/blob_index.h"
#include "db/column_family.h"
#include "db/db_iter.h"
@@ -26,9 +25,6 @@
#include "file/filename.h"
#include "port/port.h"
#include "port/stack_trace.h"
#include "rocksdb/perf_context.h"
#include "rocksdb/sst_file_writer.h"
#include "rocksdb/table.h"
#include "util/string_util.h"
#include "utilities/merge_operators.h"
@@ -45,18 +41,6 @@ void CorruptPinnedBlobIndexOnCleanup(void* arg1, void* /*arg2*/) {
} // namespace
TEST(BlobIndexTest, SameFileBlobIndex) {
std::string encoded;
BlobIndex::EncodeBlob(&encoded, kCurrentFileBlobIndexFileNumber,
/*offset=*/123, /*size=*/456, kNoCompression);
BlobIndex blob_index;
ASSERT_OK(blob_index.DecodeFrom(encoded));
EXPECT_TRUE(blob_index.IsSameFile());
EXPECT_EQ(blob_index.file_number(), kCurrentFileBlobIndexFileNumber);
EXPECT_NE(blob_index.DebugString(false).find("file:same"), std::string::npos);
}
// kTypeBlobIndex is a value type used by BlobDB only. The base rocksdb
// should accept the value type on write, and report not supported value
// for reads, unless caller request for it explicitly. The base rocksdb
@@ -139,32 +123,19 @@ class DBBlobIndexTest : public DBTestBase {
bool resolve_direct_write_value, const Version* current,
PinnableSlice* value, PinnableWideColumns* columns, Status* s,
bool* is_blob_index, bool* value_found = nullptr) {
VersionBlobFetcher blob_fetcher(current, read_options,
cfd()->blob_file_cache(),
/*allow_write_path_fallback=*/true);
return DBImpl::MaybeResolveDirectWriteValue(
read_options, key, resolve_direct_write_value, blob_fetcher, value,
read_options, key, resolve_direct_write_value, current, cfd(), value,
columns, s, is_blob_index, value_found);
}
Status MaybeResolveMemtableBlobValueForTest(
const Slice& key, const BlobFetcher* blob_fetcher, PinnableSlice* value,
PinnableWideColumns* columns, bool* did_resolve, bool* is_blob_index,
bool* value_found = nullptr) {
return DBImpl::MaybeResolveMemtableBlobValue(key, blob_fetcher, value,
columns, did_resolve,
is_blob_index, value_found);
}
Status PostprocessMemtableValueReadForTest(
const Slice& key, bool resolve_blob_backed_memtable_value,
const BlobFetcher* blob_fetcher, PinnableSlice* value,
PinnableWideColumns* columns, Status memtable_read_status,
bool* is_blob_index, bool* value_found = nullptr) {
return DBImpl::PostprocessMemtableValueRead(
key, /*timestamp=*/nullptr, resolve_blob_backed_memtable_value,
blob_fetcher, value, columns, std::move(memtable_read_status),
is_blob_index, value_found);
bool MaybeResolveMemtableBlobValueForTest(const Slice& key,
const BlobFetcher* blob_fetcher,
PinnableSlice* value,
PinnableWideColumns* columns,
Status* s, bool* is_blob_index,
bool* value_found = nullptr) {
return DBImpl::MaybeResolveMemtableBlobValue(
key, blob_fetcher, value, columns, s, is_blob_index, value_found);
}
Options GetTestOptions() {
@@ -323,65 +294,17 @@ TEST_F(DBBlobIndexTest,
value.GetSelf()->assign(blob_index.data(), blob_index.size());
value.PinSelf();
bool did_resolve = false;
Status s = Status::OK();
bool is_blob_index = true;
Status s = MaybeResolveMemtableBlobValueForTest(
Slice("key"), /*blob_fetcher=*/nullptr, &value, /*columns=*/nullptr,
&did_resolve, &is_blob_index);
ASSERT_TRUE(MaybeResolveMemtableBlobValueForTest(
Slice("key"), /*blob_fetcher=*/nullptr, &value, /*columns=*/nullptr, &s,
&is_blob_index));
ASSERT_TRUE(did_resolve);
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
ASSERT_TRUE(s.IsNotSupported()) << s.ToString();
ASSERT_TRUE(value.empty());
ASSERT_FALSE(is_blob_index);
}
TEST_F(DBBlobIndexTest, PostprocessMemtableValueReadClearsOutputsOnError) {
// Regression test for the "swallowed status" FIXME: on any error,
// PostprocessMemtableValueRead must clear the outputs and return the status,
// never leaving a half-populated result behind a non-OK status.
// Case 1: the incoming memtable read status is already non-OK. The resolve
// path is skipped; the finalization block clears outputs and returns it.
{
PinnableSlice value;
value.GetSelf()->assign("stale", 5);
value.PinSelf();
bool is_blob_index = false;
Status s = PostprocessMemtableValueReadForTest(
Slice("key"), /*resolve_blob_backed_memtable_value=*/true,
/*blob_fetcher=*/nullptr, &value, /*columns=*/nullptr,
Status::Corruption("read failed"), &is_blob_index);
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
ASSERT_TRUE(value.empty());
}
// Case 2: the incoming status is OK but the blob-resolve path itself fails (a
// blob-backed value with no fetcher). This is the actual scenario the FIXME
// referred to -- previously the non-OK status from the resolve path was
// dropped. The failure must propagate and the raw blob-index bytes must be
// cleared rather than handed back as if they were the value.
{
std::string blob_index;
BlobIndex::EncodeBlob(&blob_index, /*file_number=*/123, /*offset=*/456,
/*size=*/789, kNoCompression);
PinnableSlice value;
value.GetSelf()->assign(blob_index.data(), blob_index.size());
value.PinSelf();
bool is_blob_index = true;
Status s = PostprocessMemtableValueReadForTest(
Slice("key"), /*resolve_blob_backed_memtable_value=*/true,
/*blob_fetcher=*/nullptr, &value, /*columns=*/nullptr, Status::OK(),
&is_blob_index);
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
ASSERT_TRUE(value.empty());
}
}
TEST_F(DBBlobIndexTest, ReadOnlyGetImplReturnsBlobIndexWhenRequested) {
// Goal: cover the internal read-only GetImpl contract when the caller
// explicitly asks for raw blob-index bytes via `is_blob_index`. Recovery
@@ -407,718 +330,6 @@ TEST_F(DBBlobIndexTest, ReadOnlyGetImplReturnsBlobIndexWhenRequested) {
ASSERT_TRUE(is_blob_index);
}
// Regression test for a same-file (embedded) BlobIndex being exposed
// unresolved through an iterator. With index_type=kBinarySearchWithFirstKey
// and allow_unprepared_value (the default for DB iterators), the block-based
// table iterator can sit in the is_at_first_key_from_index_ state and return
// the raw kTypeBlobIndex internal key from the index, while value() resolves
// the same-file blob to its plain payload. DBIter then reads the stale
// kTypeBlobIndex type and routes the already-resolved plain value through the
// blob-index path, corrupting/misreading it.
//
// Forcing one entry per data block makes every embedded blob the first key of
// a block, reliably triggering the deferred-first-key state on both SeekToFirst
// and forward block transitions.
TEST_F(DBBlobIndexTest, EmbeddedBlobIteratorWithFirstKeyIndex) {
Options options = GetTestOptions();
options.create_if_missing = true;
BlockBasedTableOptions bbto;
bbto.index_type =
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey;
// Soft limit of 1 byte starts a new data block after every entry.
bbto.block_size = 1;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
DestroyAndReopen(options);
// Build an embedded-blob SST whose large values are stored as same-file blob
// records (table entries become same-file BlobIndex references).
const std::string sst_path = dbname_ + "/embedded_first_key.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big1(1024, 'x');
const std::string big2(1024, 'y');
const std::string big3(1024, 'z');
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.Put("k1", big1));
ASSERT_OK(writer.Put("k2", big2));
ASSERT_OK(writer.Put("k3", big3));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Point lookups resolve same-file blobs before exposing them, so Get works.
ASSERT_EQ(Get("k1"), big1);
ASSERT_EQ(Get("k2"), big2);
ASSERT_EQ(Get("k3"), big3);
// Iterator path: the bug manifests here.
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
iter->SeekToFirst();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k1");
EXPECT_EQ(iter->value(), big1);
iter->Next();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k2");
EXPECT_EQ(iter->value(), big2);
iter->Next();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k3");
EXPECT_EQ(iter->value(), big3);
iter->Next();
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
// Seek directly to a block whose first key is an embedded blob.
iter->Seek("k2");
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k2");
EXPECT_EQ(iter->value(), big2);
}
// Backward iteration (Prev / SeekForPrev) over an embedded-blob SST must work,
// including for wide-column entities whose same-file blob columns are resolved
// into an iterator-owned buffer. DBIter requires the underlying iterator's
// value to be pinned for backward iteration; EmbeddedBlobResolvingIterator
// therefore pins resolved values -- both whole-value blob payloads and rebuilt
// wide-column values -- and hands their cleanup to the PinnedIteratorsManager
// across repositioning. Before that, backward iteration over the wide-column
// entity returned NotSupported (or tripped the IsValuePinned() assertion).
TEST_F(DBBlobIndexTest, EmbeddedBlobBackwardIteration) {
Options options = GetTestOptions();
options.create_if_missing = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_backward.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
// Whole-value same-file blobs.
const std::string big1(1024, 'x');
const std::string big3(1024, 'z');
// Wide-column entity: a small (inline) default column plus a large
// (embedded, same-file blob) non-default column -- the mixed wide-column
// path whose resolved value lands in an iterator-owned buffer.
const std::string k2_default(2, 'd');
const std::string k2_big_col(1024, 'c');
const WideColumns k2_columns{{kDefaultWideColumnName, k2_default},
{"big", k2_big_col}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.Put("k1", big1));
ASSERT_OK(writer.PutEntity("k2", k2_columns));
ASSERT_OK(writer.Put("k3", big3));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
// Backward scan via SeekToLast + Prev.
iter->SeekToLast();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k3");
EXPECT_EQ(iter->value(), big3);
iter->Prev();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k2");
EXPECT_EQ(iter->value(), k2_default);
EXPECT_EQ(iter->columns(), k2_columns);
iter->Prev();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k1");
EXPECT_EQ(iter->value(), big1);
iter->Prev();
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
// SeekForPrev directly onto the wide-column entity, then step back.
iter->SeekForPrev("k2");
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k2");
EXPECT_EQ(iter->value(), k2_default);
EXPECT_EQ(iter->columns(), k2_columns);
iter->Prev();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k1");
EXPECT_EQ(iter->value(), big1);
}
// Regression test for T277566778. If resolving a same-file ("embedded") blob
// fails during compaction (e.g. a blob-region read fault), the error must be
// surfaced as-is. It must NOT be masked by feeding the raw, unresolved
// same-file BlobIndex downstream, where FileMetaData::UpdateBoundaries would
// (correctly) reject file number 0 and report a misleading "Invalid blob file
// number" corruption. The EmbeddedBlobResolvingIterator must not expose an
// unresolved same-file value on error: for eager (compaction) callers it
// resolves during positioning so the error is visible via status()/Valid()
// before value().
TEST_F(DBBlobIndexTest, EmbeddedBlobResolveErrorDuringCompactionNotMasked) {
Options options = GetTestOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_resolve_err.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
// Wide-column entity with a large (embedded, same-file blob) column: the
// mixed path whose resolution reads the blob region during compaction.
const std::string big_col(1024, 'c');
const WideColumns columns{{kDefaultWideColumnName, "d"}, {"big", big_col}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k2", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Add keys straddling the entity so a real compaction (not a trivial move)
// reads and resolves the embedded entity.
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k3", "v3"));
ASSERT_OK(Flush());
// Inject a resolution failure only during the compaction below, simulating a
// blob-region read fault.
const Status kInjected = Status::IOError("injected embedded blob read error");
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTable::MaybeResolveEmbeddedValue:InjectError",
[&](void* arg) { *static_cast<Status*>(arg) = kInjected; });
SyncPoint::GetInstance()->EnableProcessing();
const Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// The compaction must fail with the injected read error, NOT a masked
// "Invalid blob file number" corruption.
ASSERT_NOK(s);
EXPECT_TRUE(s.IsIOError()) << s.ToString();
EXPECT_EQ(s.ToString().find("Invalid blob file number"), std::string::npos)
<< s.ToString();
}
// Regression test for T277310719, the whole-value (kTypeBlobIndex) sibling of
// the wide-column-entity case above (T277566778). Both share one root cause: on
// a blob-region resolution error during compaction, the
// EmbeddedBlobResolvingIterator must not fall back to exposing the raw,
// unresolved same-file value.
//
// The whole-value variant is more dangerous than the entity variant because it
// escapes the FileMetaData::UpdateBoundaries tripwire: ResolveKeyType()
// rewrites the key type kTypeBlobIndex -> kTypeValue (no I/O, always succeeds),
// so on a masked resolution error the emitted entry is {kTypeValue, raw
// BlobIndex bytes}. UpdateBoundaries only scans kTypeBlobIndex/entity types, so
// unlike the entity variant it does NOT reject it -- the masked error slips
// through, a corrupt {kTypeValue, BlobIndex} record is written to the
// compaction output, and a later point lookup reads those raw BlobIndex bytes
// back as the value (in db_stress this surfaced as the
// ExpectedValue::IsValueBaseValid assertion, not "Invalid blob file number").
// The eager (compaction) resolver must surface the error via status()/Valid()
// before value() is consumed.
TEST_F(DBBlobIndexTest,
EmbeddedBlobResolveErrorWholeValueDuringCompactionNotMasked) {
Options options = GetTestOptions();
options.create_if_missing = true;
options.disable_auto_compactions = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_resolve_err_wholevalue.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
// A large whole value is stored as a same-file blob, so the table entry
// becomes a same-file (file number 0) BlobIndex (kTypeBlobIndex) -- the
// whole-value path whose resolution reads the blob region during compaction.
const std::string big_val(1024, 'b');
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.Put("k2", big_val));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Add keys straddling the embedded blob so a real compaction (not a trivial
// move) reads and resolves it.
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k3", "v3"));
ASSERT_OK(Flush());
// Inject a resolution failure only during the compaction below, simulating a
// blob-region read fault.
const Status kInjected = Status::IOError("injected embedded blob read error");
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTable::MaybeResolveEmbeddedValue:InjectError",
[&](void* arg) { *static_cast<Status*>(arg) = kInjected; });
SyncPoint::GetInstance()->EnableProcessing();
const Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// The compaction must fail with the injected read error. Before the fix it
// instead succeeded (masking the error) and persisted a {kTypeValue, raw
// BlobIndex} record; it must not do that, and it must not report a misleading
// "Invalid blob file number" corruption either.
ASSERT_NOK(s);
EXPECT_TRUE(s.IsIOError()) << s.ToString();
EXPECT_EQ(s.ToString().find("Invalid blob file number"), std::string::npos)
<< s.ToString();
}
// When a blob cache is configured, same-file ("embedded") blob reads should go
// through BlobSource: the first read misses + inserts + does a disk read, and
// the second read of the same blob is served from the blob cache. This holds on
// both the Get() and the iterator paths.
TEST_F(DBBlobIndexTest, EmbeddedBlobBlobCacheHitMiss) {
Options options = GetTestOptions();
options.create_if_missing = true;
options.statistics = CreateDBStatistics();
LRUCacheOptions co;
co.capacity = 8 << 20;
options.blob_cache = NewLRUCache(co);
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_cache.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big1(1024, 'x');
const std::string big2(1024, 'y');
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.Put("k1", big1));
ASSERT_OK(writer.Put("k2", big2));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
Statistics* const stats = options.statistics.get();
// First Get: blob cache miss -> disk read -> cache insert.
ASSERT_OK(stats->Reset());
get_perf_context()->Reset();
SetPerfLevel(kEnableCount);
ASSERT_EQ(Get("k1"), big1);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_MISS), 1);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_HIT), 0);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_ADD), 1);
EXPECT_GT(stats->getTickerCount(BLOB_DB_BLOB_FILE_BYTES_READ), 0);
EXPECT_EQ(get_perf_context()->blob_read_count, 1);
// Second Get of the same key: blob cache hit, no disk read, no insert.
ASSERT_OK(stats->Reset());
get_perf_context()->Reset();
ASSERT_EQ(Get("k1"), big1);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_HIT), 1);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_MISS), 0);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_ADD), 0);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_BLOB_FILE_BYTES_READ), 0);
EXPECT_EQ(get_perf_context()->blob_read_count, 0);
EXPECT_EQ(get_perf_context()->blob_cache_hit_count, 1);
// Iterator path: first read of a fresh key misses + inserts.
ASSERT_OK(stats->Reset());
{
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
iter->Seek("k2");
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->value(), big2);
}
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_MISS), 1);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_ADD), 1);
// Iterator path: second read of the same key hits the blob cache.
ASSERT_OK(stats->Reset());
{
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
iter->Seek("k2");
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->value(), big2);
}
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_HIT), 1);
EXPECT_EQ(stats->getTickerCount(BLOB_DB_CACHE_ADD), 0);
SetPerfLevel(kDisable);
}
// With the blob cache and block cache pointing at the same Cache, the SST-like
// cache-key scheme (offset >> 2 on the SST's base key) keeps embedded blob
// records disjoint from the SST's data blocks. Reading embedded blobs must not
// alias or evict data blocks, and values must remain correct.
TEST_F(DBBlobIndexTest, EmbeddedBlobSharedBlockAndBlobCache) {
Options options = GetTestOptions();
options.create_if_missing = true;
options.statistics = CreateDBStatistics();
LRUCacheOptions co;
co.capacity = 16 << 20;
auto shared_cache = NewLRUCache(co);
options.blob_cache = shared_cache;
BlockBasedTableOptions bbto;
bbto.block_cache = shared_cache;
bbto.cache_index_and_filter_blocks = true;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_shared.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big1(1024, 'x');
const std::string big2(1024, 'y');
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.Put("k1", big1));
ASSERT_OK(writer.Put("k2", big2));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
Statistics* const stats = options.statistics.get();
// Values are correct with a shared cache, and re-reads hit both the block
// cache (data blocks) and the blob cache (embedded payloads) independently.
ASSERT_EQ(Get("k1"), big1);
ASSERT_EQ(Get("k2"), big2);
ASSERT_EQ(Get("k1"), big1);
ASSERT_EQ(Get("k2"), big2);
EXPECT_GT(stats->getTickerCount(BLOB_DB_CACHE_HIT), 0);
EXPECT_GT(stats->getTickerCount(BLOCK_CACHE_HIT), 0);
// Iterator yields correct values too.
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
iter->SeekToFirst();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k1");
EXPECT_EQ(iter->value(), big1);
iter->Next();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
EXPECT_EQ(iter->key(), "k2");
EXPECT_EQ(iter->value(), big2);
iter->Next();
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
}
// Point GetEntity / MultiGetEntity of a wide-column entity whose non-default
// columns are embedded (same-file) blobs must return the correct columns and
// resolve them zero-copy: each resolved blob column value points straight at
// its pinned blob-cache buffer (identical across reads, and in a separate
// buffer per column), rather than being copied into one re-serialized entity.
TEST_F(DBBlobIndexTest, EmbeddedBlobWideColumnGetEntityZeroCopy) {
Options options = GetTestOptions();
options.create_if_missing = true;
options.statistics = CreateDBStatistics();
LRUCacheOptions co;
co.capacity = 8 << 20;
options.blob_cache = NewLRUCache(co);
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_zerocopy.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string small_default = "d"; // inline default column
const std::string big1(1024, 'a'); // same-file blob column
const std::string big2(2048, 'b'); // same-file blob column
const WideColumns columns{
{kDefaultWideColumnName, small_default}, {"big1", big1}, {"big2", big2}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
const char* big1_ptr = nullptr;
const char* big2_ptr = nullptr;
{
PinnableWideColumns result;
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k",
&result));
ASSERT_EQ(result.columns(), columns);
// columns() is sorted by name -> [default(""), big1, big2].
big1_ptr = result.columns()[1].value().data();
big2_ptr = result.columns()[2].value().data();
// Multi-buffer: each blob column lives in its own backing buffer, so the
// two values are not laid out contiguously (as they would be in a single
// re-serialized V1 entity).
ASSERT_NE(big1_ptr + big1.size(), big2_ptr);
// Pointer stability across move (address-stable backing nodes).
PinnableWideColumns moved(std::move(result));
ASSERT_EQ(moved.columns(), columns);
ASSERT_EQ(moved.columns()[1].value().data(), big1_ptr);
ASSERT_EQ(moved.columns()[2].value().data(), big2_ptr);
}
// A second read serves the same pinned blob-cache buffers (zero-copy): the
// resolved column values point at the very same addresses, which a per-read
// re-serialization would not.
{
PinnableWideColumns result;
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k",
&result));
ASSERT_EQ(result.columns(), columns);
ASSERT_EQ(result.columns()[1].value().data(), big1_ptr);
ASSERT_EQ(result.columns()[2].value().data(), big2_ptr);
}
// MultiGetEntity: same correctness and zero-copy (cache-hit) guarantee.
{
const std::array<Slice, 1> keys{Slice("k")};
std::array<PinnableWideColumns, 1> results;
std::array<Status, 1> statuses;
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), keys.size(),
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_EQ(results[0].columns(), columns);
ASSERT_EQ(results[0].columns()[1].value().data(), big1_ptr);
ASSERT_EQ(results[0].columns()[2].value().data(), big2_ptr);
}
}
// A wide-column entity whose default column is itself an embedded (same-file)
// blob must resolve correctly on the plain Get path (default-column
// extraction) as well as GetEntity, matching the values written.
TEST_F(DBBlobIndexTest, EmbeddedBlobWideColumnGetDefaultColumn) {
Options options = GetTestOptions();
options.create_if_missing = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_default.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big_default(1024, 'x'); // same-file blob default column
const std::string small_attr = "s"; // inline non-default column
const WideColumns columns{{kDefaultWideColumnName, big_default},
{"attr", small_attr}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Plain Get returns the (blob-backed) default column value.
PinnableSlice value;
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), "k", &value));
ASSERT_EQ(value, big_default);
// GetEntity returns all columns.
PinnableWideColumns result;
ASSERT_OK(
db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k", &result));
ASSERT_EQ(result.columns(), columns);
}
// Merge over an embedded-blob wide-column base entity must resolve the base
// entity's same-file default column before merging, and GetMergeOperands must
// return the resolved default column as the base operand.
TEST_F(DBBlobIndexTest, EmbeddedBlobWideColumnMergeBase) {
Options options = GetTestOptions(); // merge_operator = StringAppend(',')
options.create_if_missing = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_merge.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big_default(1024, 'D'); // same-file blob default column
const WideColumns columns{{kDefaultWideColumnName, big_default},
{"attr", "s"}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Newer merge operand lives in the memtable; the embedded entity base lives
// in the ingested SST, so the merge resolves the base via the same-file path.
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), "k", "op1"));
PinnableSlice value;
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), "k", &value));
ASSERT_EQ(value, big_default + "," + "op1");
// GetMergeOperands returns the resolved base default column plus the operand.
std::array<PinnableSlice, 8> operands;
GetMergeOperandsOptions get_merge_opts;
get_merge_opts.expected_max_number_of_operands =
static_cast<int>(operands.size());
int num_operands = 0;
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
"k", operands.data(), &get_merge_opts,
&num_operands));
ASSERT_EQ(num_operands, 2);
ASSERT_EQ(operands[0], big_default);
ASSERT_EQ(operands[1], "op1");
}
// With read_tier == kBlockCacheTier, an unresolved same-file blob column of a
// wide-column entity must surface Incomplete (never a raw same-file BlobIndex).
TEST_F(DBBlobIndexTest, EmbeddedBlobWideColumnGetEntityBlockCacheTier) {
Options options = GetTestOptions();
options.create_if_missing = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_blockcache.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big1(1024, 'a');
const WideColumns columns{{kDefaultWideColumnName, "d"}, {"big", big1}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Warm the block cache so the data block read succeeds under kBlockCacheTier
// and the lookup actually reaches same-file blob column resolution.
{
PinnableWideColumns result;
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k",
&result));
ASSERT_EQ(result.columns(), columns);
}
ReadOptions read_options;
read_options.read_tier = kBlockCacheTier;
PinnableWideColumns result;
Status s =
db_->GetEntity(read_options, db_->DefaultColumnFamily(), "k", &result);
ASSERT_TRUE(s.IsIncomplete()) << s.ToString();
}
// Companion to the test above (genuine miss -> Incomplete): with a blob cache
// configured, a warm read populates the embedded record into the blob cache, so
// a subsequent kBlockCacheTier GetEntity serves it from cache instead of
// returning Incomplete. This exercises the non-lazy Get/GetEntity embedded path
// (get_context -> EmbeddedAwareBlobFetcher -> GetSameFileBlob whole ->
// ResolveEmbeddedBlobCached), which shares the block-cache-tier behavior with
// the lazy resolver (a same-file column already in the blob cache is served).
TEST_F(DBBlobIndexTest,
EmbeddedBlobWideColumnGetEntityBlockCacheTierServesCached) {
Options options = GetTestOptions();
options.create_if_missing = true;
LRUCacheOptions co;
co.capacity = 8 << 20;
options.blob_cache = NewLRUCache(co);
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_blockcache_hit.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big1(1024, 'a');
const WideColumns columns{{kDefaultWideColumnName, "d"}, {"big", big1}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
// Warm both the block cache (data block) and the blob cache (embedded record)
// with a full read.
{
PinnableWideColumns result;
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k",
&result));
ASSERT_EQ(result.columns(), columns);
}
// The embedded record is now cached, so a block-cache-only read serves it.
ReadOptions read_options;
read_options.read_tier = kBlockCacheTier;
PinnableWideColumns result;
ASSERT_OK(
db_->GetEntity(read_options, db_->DefaultColumnFamily(), "k", &result));
ASSERT_EQ(result.columns(), columns);
}
// A corrupt embedded blob record backing a wide-column column must surface an
// error on GetEntity, not expose the raw same-file BlobIndex.
// A failure resolving an embedded blob record backing a wide-column column
// (e.g. a bad record or blob-region read fault) must surface on GetEntity, not
// expose the raw same-file BlobIndex. The failure is injected via a sync point
// so the test is independent of the on-disk byte layout (e.g. encrypted env).
TEST_F(DBBlobIndexTest, EmbeddedBlobWideColumnGetEntityCorruptionSurfaced) {
Options options = GetTestOptions();
options.create_if_missing = true;
DestroyAndReopen(options);
const std::string sst_path = dbname_ + "/embedded_wc_corrupt.sst";
SstFileWriterEmbeddedBlobOptions embedded_blob_options;
embedded_blob_options.min_blob_size = 8;
const std::string big_col(1024, 'Z');
const WideColumns columns{{kDefaultWideColumnName, "d"}, {"big", big_col}};
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.OpenWithEmbeddedBlobs(sst_path, embedded_blob_options));
ASSERT_OK(writer.PutEntity("k", columns));
ASSERT_OK(writer.Finish());
ASSERT_OK(db_->IngestExternalFile({sst_path}, IngestExternalFileOptions()));
const Status kInjected = Status::Corruption("injected embedded blob error");
SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTable::MaybeResolveEmbeddedValue:InjectError",
[&](void* arg) { *static_cast<Status*>(arg) = kInjected; });
SyncPoint::GetInstance()->EnableProcessing();
PinnableWideColumns result;
const Status s =
db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), "k", &result);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
}
class PlainBlobValueFilterV3 : public CompactionFilter {
public:
PlainBlobValueFilterV3(std::atomic<int>* filter_call_count,
-90
View File
@@ -1,90 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include <cstddef>
#include <cstdint>
#include "db/blob/blob_constants.h"
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/status.h"
namespace ROCKSDB_NAMESPACE {
class BlobIndex;
class PinnableSlice;
struct ReadOptions;
// One request for SameFileBlobReader::MultiGetSameFileBlob (the batched
// counterpart of GetSameFileBlob). See GetSameFileBlob for the per-field
// semantics (range_length == kWholeBlobLength selects the whole value).
struct SameFileBlobReadRequest {
const BlobIndex* blob_index = nullptr;
uint64_t range_offset = 0;
size_t range_length = kWholeBlobLength;
BlobVerifyPolicy verify_policy = BlobVerifyPolicy::kVerifyIfNoAmplification;
PinnableSlice* result = nullptr;
Status* status = nullptr;
};
// Narrow interface for resolving a same-file ("embedded") blob reference
// against the physical file that currently holds the table entry. Implemented
// by BlockBasedTable, which owns the reader context (file, footer, blob cache)
// needed to read an embedded blob record.
//
// Kept in its own low-level header, free of any table-layer dependency, so that
// db/blob (EmbeddedAwareBlobFetcher) and table/get_context can depend on just
// this interface without pulling in BlockBasedTable. ReadPathBlobResolver (the
// lazy-resolution read path) can likewise hold a const SameFileBlobReader*.
class SameFileBlobReader {
public:
virtual ~SameFileBlobReader() = default;
// Resolves a same-file ("embedded") blob reference (one for which
// blob_index.IsSameFile() is true) into `value`, pinning the payload
// zero-copy (from the blob cache or an owned buffer).
//
// range_length == kWholeBlobLength resolves the whole value. Any other length
// resolves only the sub-range [range_offset, range_offset + range_length),
// reading just those bytes on a cache miss -- skipping whole-record checksum
// verification (a strict sub-range cannot cover it) and never populating the
// blob cache; valid only for an uncompressed record and a verify_policy other
// than kVerifyIfPresent, with range_offset + range_length <=
// blob_index.size().
//
// verify_policy controls whole-record checksum verification (see
// BlobVerifyPolicy); a whole read under kVerifyIfPresent verifies even when
// ReadOptions::verify_checksums is off. read_tier == kBlockCacheTier without
// a cached record yields Status::Incomplete; a bad record yields
// Status::Corruption.
virtual Status GetSameFileBlob(const ReadOptions& read_options,
const BlobIndex& blob_index,
uint64_t range_offset, size_t range_length,
BlobVerifyPolicy verify_policy,
PinnableSlice* value) const = 0;
// Batched counterpart of GetSameFileBlob: resolves `num_reads` same-file blob
// references (whole or sub-range, mixed) against this reader, writing each
// outcome to *reqs[i].status. Implementations may coalesce the underlying
// disk reads into a single MultiRead. The default implementation simply loops
// over GetSameFileBlob (correct, but not coalesced); BlockBasedTable
// overrides it to coalesce reads of embedded records in the same SST.
virtual void MultiGetSameFileBlob(const ReadOptions& read_options,
size_t num_reads,
SameFileBlobReadRequest* reqs) const {
for (size_t i = 0; i < num_reads; ++i) {
SameFileBlobReadRequest& req = reqs[i];
const Status s =
GetSameFileBlob(read_options, *req.blob_index, req.range_offset,
req.range_length, req.verify_policy, req.result);
if (req.status) {
*req.status = s;
}
}
}
};
} // namespace ROCKSDB_NAMESPACE
+1 -4
View File
@@ -179,8 +179,6 @@ Status BuildTable(
TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes);
#endif // !NDEBUG
FileOptions fo_copy = file_options;
fo_copy.open_contract = FileOpenContract::kNoReopenForWrite |
FileOpenContract::kNoReadersWhileOpenForWrite;
fo_copy.write_hint = write_hint;
IOStatus io_s = NewWritableFile(fs, fname, &file, fo_copy);
assert(s.ok());
@@ -207,8 +205,7 @@ Status BuildTable(
std::move(file), fname, file_options, ioptions.clock, io_tracer,
ioptions.stats, Histograms::SST_WRITE_MICROS, ioptions.listeners,
ioptions.file_checksum_gen_factory.get(),
tmp_set.Contains(FileType::kTableFile),
tmp_set.Contains(FileType::kTableFile)));
tmp_set.Contains(FileType::kTableFile), false));
builder = NewTableBuilder(tboptions, file_writer.get());
}
+1821 -6888
View File
File diff suppressed because it is too large. Load diff
Loaded 100 of 632 files, more files were not shown because too many files have changed in this diff. Show more