Compare commits

..
58 Commits
Author SHA1 Message Date
anand1976 26432d79f7 Merge pull request #15360 from evolvedbinary/hotfix/11.7.fb/windows-compilation
Fix for Windows build issue
2026-10-15 10:46:45 -07:00
anand76 557960bd9f Revert "Fix Windows build issue in trie_index_factory.h"
This reverts commit 96012d9954.
2026-10-15 10:38:31 -07:00
Anand Ananthabhotla 6720218931 Make InjectedErrorLog::Entry 256 bytes on 32-bit targets (#15388)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15388

`InjectedErrorLog::Entry` in `utilities/fault_injection_fs.h` is written to the injected error log in its native layout, and a `static_assert` requires `sizeof(Entry) == 256` so the record size stays stable. That assert fails on i386 builds.

Root cause: the fields add up to only 252 bytes. On targets where `alignof(uint64_t)` is 8 (x86_64, aarch64, armv7), the compiler adds 4 bytes of implicit tail padding, so the size is 256. On i386, `uint64_t` members are 4-byte aligned inside a struct, so there is no tail padding and the size is 252.

Fix: add an explicit trailing `uint8_t padding[4]` field so the size no longer depends on `uint64_t` alignment.

Compatibility: on 64-bit targets the new field takes the place of the old implicit tail padding, so every field offset and the 256-byte record layout are unchanged. `Record()` value-initializes the entry (`Entry entry{}`), so the padding bytes are always written as zero. No decoder in the repo depends on the padding bytes.

___

landed-with-radar-review

Differential Revision: D124873369

fbshipit-source-id: f5c6f43ca85013b0e769853ad13b8d4f3f89b958
2026-10-13 10:54:47 -07:00
xingbowang 7ced0aaac9 Fix big-endian builds for the fault-injection filesystem (#15297)
Summary:
The persistent injected-error log writes `InjectedErrorLog::Entry` objects using their native in-memory layout, while the log format's consumer expects little-endian multi-byte fields. The existing `static_assert(port::kLittleEndian)` protected that format by rejecting every big-endian build that included the fault-injection filesystem, even though the log is an optional diagnostic.

This change keeps RocksDB and the fault-injection filesystem available on supported big-endian platforms while disabling only the incompatible persistent log. Log-file creation now returns an inactive descriptor when `port::kLittleEndian` is false, which makes `Record()` and `Flush()` remain safe no-ops. The fixed 256-byte entry-size assertion remains in place, and format-dependent tests are bypassed on big-endian targets.

The change also adds a release note and documents big-endian support plus the preferred capability-check pattern for optional architecture-specific features.

## Compatibility and limitations

- Little-endian behavior and the on-disk log layout are unchanged.
- On big-endian platforms, configuring an injected-error log path does not create or populate a log file; the rest of fault injection remains available.
- Windows already leaves this POSIX-backed diagnostic disabled.
- This does not introduce a portable log encoding. Supporting persistent injected-error logs on big-endian platforms would require a format change.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15297

Test Plan:
- `build_tools/rockstest.sh fault_injection_fs_test '--gtest_filter=InjectedErrorLogTest.*'` (little-endian macOS): all 5 tests passed.
- `make check-sources`
- `git diff --check upstream/main...2026_10_01_endian_fix`

Native big-endian build and test execution were not available locally; that path relies on the compile-time `port::kLittleEndian` capability and should be covered by big-endian CI.

Reviewed By: jaykorean

Differential Revision: D122880760

Pulled By: xingbowang

fbshipit-source-id: 59dae9661a47608aa12f41078ad1b3ef0fa7231e
2026-10-13 10:54:38 -07:00
anand76 96012d9954 Fix Windows build issue in trie_index_factory.h 2026-10-13 10:54:31 -07:00
Adam Retter 626d471544 Fix for Windows build issue (see: https://github.com/facebook/rocksdb/issues/14977) 2026-10-08 11:10:05 +02:00
anand1976 1129d3510f Update HISTORY.md for 11.7.0 release 2026-07-16 13:27:49 -07:00
Laurynas Biveinis d461606f0f Make range-lock waits interruptible with SetIsKilledCallback (#14927)
Summary:
Add RangeLockManagerHandle::SetIsKilledCallback, a std::function<bool()> predicate mirroring SetEscalationBarrierFunc. When installed, RangeTreeLockManager wakes the toku lock_request::wait loop every 100ms to poll it; if it fires, the wait completes with a new DB_LOCK_INTERRUPTED result mapped to Status::Aborted(), so a blocked GetRangeLock() returns promptly instead of waiting out the lock timeout. Range-lock users that install no callback keep the original wait-until-grant-or-timeout behavior (no extra wakeups).

Also remove the unused toku kill_waiter abort path (lock_request and locktree_manager), which had no caller.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14927

Test Plan:
`build_tools/rocksptest.sh range_locking_test` passes, including the two new tests. KilledCallbackAbortsWait completes in ~90ms despite a 100s lock timeout, confirming the kill callback aborts the wait promptly; KilledCallbackFalseStillTimesOut confirms an installed-but-false callback still times out.

Tested end-to-end together with the MyRocks side in D110890436: with rocksdb_use_range_locking=ON, a KILL QUERY parked on a conflicting range-lock wait is interrupted promptly (rocksdb.range_locking MTR suite) instead of waiting out rocksdb_lock_wait_timeout.

Reviewed By: pdillinger

Differential Revision: D111663405

Pulled By: laurynas-biveinis

fbshipit-source-id: 4819e09bde66f4e932aef74f6adfd14789bb1425
2026-07-15 21:21:45 -07:00
Peter Dillinger c9536aaef7 Fix quadratic point-in-time MANIFEST recovery in VersionBuilder (#14929)
Summary:
Point-in-time MANIFEST replay -- best-efforts recovery (VersionSet::TryRecoverFromOneManifest) and secondary/follower catch-up (ManifestTailer) -- was quadratic in the number of MANIFEST records.

d458331ee (D61171320, "Move file tracking in VersionEditHandlerPointInTime to VersionBuilder") took a clean approach: it consolidated applying a VersionEdit and checking Version completeness into a single VersionBuilder implementation, so those checks were no longer duplicated between the handler and the builder. To let VersionEditHandlerPointInTime still build a Version reflecting the state before a valid->invalid edit, it added a save point -- but that save point proved inefficient: it was implemented by deep-copying the entire VersionBuilder::Rep before every edit (all of levels_[].added_files with a ref bump per file, table_file_levels_, found_files_, mutable_blob_file_metas_, ...). With N edits accumulating O(F) live files that is O(N*F) time and memory, and in the common healthy case the copy is pure waste since no Version is built during replay.

D110842287 (66bccb467, "Store BySmallestKey by value to avoid shared_ptr atomic copy in Rep") addressed a CPU-overhead symptom that had been misattributed to the shared_ptr atomic copies in that per-edit Rep copy. The real expense was copying Rep's containers -- notably table_file_levels_ -- on every edit. This change keeps d458331ee's clean single-implementation design but makes the save point cheap, removing the copy entirely:

- Replace the whole-Rep save point with a single-edit undo. Apply() records a compact, self-inverse undo of just the current edit (the pre-edit value of each touched key), and the PIT handler builds the pre-edit Version by rolling that one edit back, then redoing it to continue replay. This is O(edit) per edit, single-copy memory, and adds no extra file-verification I/O except on the rare valid->invalid negative edge. Also removes the Rep copy constructor.

- Store the undo registries in UnorderedMap (hash map) where ordering is not needed, which reduces the remaining per-edit CPU further. added_files and deleted_files keep an ordered map keyed by (level, file number), which trivial moves (same file number at two levels in one edit) require.

- Group the scalar/flag state mutated during Apply into a MutableScalars base so the undo can snapshot/restore it with a single copy/swap without duplicating the field list. Add util/cast_util.h up_cast<Base> for the explicit, compile-time-checked upcasts this uses instead of raw static_cast.

- Generalize D110842287: also store the stateless L0 comparators level_zero_cmp_by_epochno_ and level_zero_cmp_by_seqno_ by value rather than shared_ptr, dropping their make_shared allocation and atomic refcounting.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14929

Test Plan: Debug build (asserts + force_consistency_checks on). The single-edit rollback path is exercised by the existing recovery coverage, all passing: version_builder_test, version_set_test, db_secondary_test (ManifestTailer), db_follower_test (PartialVersionRecovery = the negative-edge rebuild), and the db_basic_test best-efforts suite -- including BestEffortsRecoverIncompleteVersion with blob-file variants (blob addition rolled back/redone) and BestEffortRecoveryFailureWithTableCacheUseAfterFree (FileMetaData/table-cache lifecycle) -- plus corruption_test.

Reviewed By: joshkang97

Differential Revision: D111753669

Pulled By: pdillinger

fbshipit-source-id: 7e6b5238f55cd906b1d4ff0044aeb0a02ec1cdcd
2026-07-15 13:34:03 -07:00
Jay Huh cf22ee8f5b Harden RocksDB PR comment artifact downloads (#14941)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14941

Download GitHub Actions artifacts into `_artifact` before reading comment result files so untrusted artifacts cannot overwrite checked-out workflow helper scripts. Keep `post-pr-comment.js` loaded only from `.github/scripts` and remove unnecessary inherited secrets from the review-comment wrapper workflows.

Reviewed By: pdillinger

Differential Revision: D112129548

fbshipit-source-id: c807eab2197018b0672a5e1d9710e4c6bfcfd0ac
2026-07-15 12:54:46 -07:00
Peter Dillinger c7f7b1b2ae Skip redundant DeleteWalsBefore during recovery when markers already advanced (#14918)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14918

When `optimize_manifest_for_recovery=true` and `track_and_verify_wals_in_manifest=true`, the close-time code (`MaybeWriteWalMarkersToManifestOnClose`) already writes `DeleteWalsBefore(max_wal+1)` to the MANIFEST. On the subsequent clean reopen, recovery was unconditionally emitting the same `DeleteWalsBefore` edit, causing 43 bytes of unnecessary MANIFEST growth. This violated the zero-growth guarantee that the stress test STRICT mode verifies, triggering `exit(1)` which then caused a secondary `property_info != nullptr` assertion in `DumpStats()` (from the Timer thread running during static destructor teardown).

The fix adds the same monotonicity guard (`new_min_log > GetMinWalNumberToKeep()`) that is already used at all four other `DeleteWalsBefore` call sites (db_impl.cc:748, db_impl_files.cc:906, db_impl_write.cc:3331, memtable_list.cc:1015). This skips the redundant write when the WalSet min was already advanced at close time, while still correctly emitting it after a crash where the close-time markers were never written.

Reviewed By: xingbowang

Differential Revision: D110713771

fbshipit-source-id: 3592d11b20a9e660f1fea58a7a72565b2030c6d8
2026-07-15 12:12:10 -07:00
Josh Kang dc72a64a5b Flush no-WAL live-file data with LockWAL (#14931)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14931

The failing crash-test scenario combines `disable_wal=1`, `LockWAL()`, and backup/checkpoint live-file-copy verification. A WAL-disabled write can leave a key only in the memtable. The old live-file path skipped `FlushForGetLiveFiles()` whenever `LockWAL()` was active, so the copied SST/WAL file set could omit that memtable-only key even though the original DB still served it. Restore verification could then report the key as present in the original DB but missing from the restored backup/checkpoint.

Fix live-file capture to flush unpersisted WAL-disabled data even while `LockWAL()` is active. If all data is already persisted, the path still skips the flush while WAL is locked. If unpersisted data exists, backup/checkpoint can now block behind `LockWAL()` until another thread calls `UnlockWAL()` and the flush can safely proceed.

To avoid self-deadlock, track `LockWAL()` ownership by thread with recursive counts. `UnlockWAL()` now has to be called by the same thread that successfully called `LockWAL()`, and wait=true flushes called by that same owner return `Aborted("Likely deadlock as the same thread called LockWAL()")` instead of waiting forever. Cross-thread flushes remain allowed and wait for all `LockWAL()` owners to unlock.

Also audited MyRocks and can confirm they already conform to the Lock/Unlock on same thread semantics.

Reviewed By: pdillinger

Differential Revision: D111793089

fbshipit-source-id: 1bbb03b25d00d47440126cc6bb4ce7410302e25b
2026-07-15 12:07:38 -07:00
Peter Dillinger 74aba599ab Fix WriteUnprepared transaction rollback after commit write failure (#14920)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14920

When a WriteUnprepared transaction's commit write fails while two_write_queues
is enabled (e.g. due to an I/O error), CommitInternal() unconditionally cleared
unprep_seqs_ and the flushed/unflushed save points before returning the error.
That left the transaction with no record of its prepared sequence numbers, so a
subsequent Rollback() -- the natural way for a caller to recover from a failed
commit -- constructed a WriteUnpreparedCommitEntryPreReleaseCallback over an
empty unprep_seqs_ and tripped assert(unprep_seqs.size() > 0) in its
constructor. In release builds this would instead corrupt commit-cache
accounting rather than aborting.

Fix: only clear unprep_seqs_ and the save points on the commit success path. On
the two-write-queues commit-write failure path we still remove the prepared
entry that AddPreparedCallback may have added for commit_batch_seq (that seq is
never stored in unprep_seqs_, so a later Rollback cannot clean it up), but
unprep_seqs_ is otherwise preserved so the transaction can still be rolled back
once the caller resumes the DB from the write error.

Originally authored by Devmate trigger

Reviewed By: xingbowang

Differential Revision: D110271443

fbshipit-source-id: 7d991e01ddc74ccd170cbc25f6af160c5f3d5663
2026-07-14 16:12:13 -07:00
xingbowang 329d9e7adf Add blog post on Blob Direct Write and partitioned blob files (#14873)
Summary:
- Add a RocksDB blog post explaining Blob Direct Write and partitioned blob files.
- Cover the write-path transformation to `BlobIndex`, partition selection, blob-file lifecycle, read fallback for in-flight direct-write files, and the current v1 scope.
- Describe policy-driven grouping use cases such as TTL bucketing with Universal Compaction and wide-column metadata.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14873

Test Plan: {F1991646296}

Reviewed By: joshkang97

Differential Revision: D109564817

Pulled By: xingbowang

fbshipit-source-id: 712fc0b6a8ffe68e2068af756dceb421c65a9af8
2026-07-14 07:31:34 -07:00
Josh Kang 5b1c3b37ac Disable codex reviews on PRs (#14933)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14933

Codex reviews currently have some issues with the API Key. Will disable them from reviews to prevent noise until it is properly fixed.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14932

Differential Revision: D111798073

Pulled By: joshkang97

fbshipit-source-id: 9340b89371185979e34465325c20d02a494990e5
2026-07-13 15:10:40 -07:00
Jay Huh eca1a114e0 Count unreleased MultiScan prefetch blocks as wasted (#14926)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14926

This fixes a gap in the MultiScan prefetch waste accounting introduced by https://github.com/facebook/rocksdb/pull/14876. That change added reverse MultiScan support and introduced `unreleased_prefetch_blocks_` to track prefetched blocks still owned by the `ReadSet`, because reverse scans can prefetch a suffix of the collected block list and release blocks in non-prefix order. However, those unreleased prefetched blocks were only released during `MultiScanIndexIterator` destruction; they were not included in `MULTISCAN_PREFETCH_BLOCKS_WASTED`. As a result, scans that stopped before consuming all prefetched blocks underreported wasted prefetch blocks.

This change counts unreleased prefetched blocks as wasted before destructor cleanup, while avoiding counting the currently positioned block as wasted when it has been reached.

Reviewed By: xingbowang

Differential Revision: D111531409

fbshipit-source-id: 5e605ce686037256bf6847ab7ca363c3f7701401
2026-07-11 08:20:42 -07:00
Hui Xiao 7c80a5ac85 Size-tolerant deserialization for compaction service response's counts array (#14807)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14807

The "counts" array in CompactionServiceResult (one compaction count per CompactionReason) is non-critical information. During cross-process remote compaction the primary and the remote worker can run different RocksDB versions and therefore disagree on kNumOfReasons, so the serialized counts array can have a different length than the reader expects. The plain OptionTypeInfo::Array parser rejects any element-count mismatch, which fails deserialization of the entire result over this non-critical field.

This makes deserialization of "counts" size-tolerant via SizeTolerantDeserializeArray, which overrides only the parse callback: extra trailing elements are dropped and missing trailing elements are zero-filled, so a length mismatch no longer fails deserialization. Serialization and comparison are inherited from Array<T, kSize> unchanged.

The serialized width is intentionally kept at kNumOfReasons - 1 here, so this change does not alter what is written -- only the read path becomes tolerant. Widening the serialized width to the full kNumOfReasons is done as a separate follow-up change, which must not land until this tolerant read has been deployed to every primary (including rollback targets), so that no reader still rejects a wider result.

== Test plan ==
**Legend**

CompactionReason is an enum whose last entry is kNumOfReasons (currently 21). CompactionStats::counts is an int[kNumOfReasons] holding one count per reason; it is serialized into the remote compaction result and read back by the primary.
- "narrow" = kNumOfReasons - 1 counts (omits the newest reason); what current code writes.
- "wide" = kNumOfReasons counts (includes the newest reason).
- strict reader: accepts only its exact expected element count; any other count fails deserialization (the plain OptionTypeInfo::Array behavior).
- tolerant reader: accepts any count -- more elements than expected are dropped, fewer are zero-filled; never fails on a count mismatch (this change, SizeTolerantDeserializeArray).

**Unit test**

CompactionServiceTest.CompatCheckCountsWidthTolerance -- verifies that deserializing "counts" tolerates a serialized element count equal to, one greater than, or one less than the reader's expected width (extras dropped, missing zero-filled), and that the binary serializes exactly the expected number of elements.

**Manual cross-version verification (ldb remote_compaction)**

Test 1: baseline (unchanged behavior) -- expect-narrow strict-read primary reads narrow counts written by a worker
```
$ strict_narrow remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ strict_narrow remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4913 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Test 2: expect-narrow tolerant-read primary reads the narrow counts a worker writes
```
$ strict_narrow remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_narrow remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4912 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Test 3: expect-narrow tolerant-read primary reads the wide counts a worker writes (extra reason dropped)
```
$ tolerant_wide remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_narrow remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4915 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Test 4: expect-wide, tolerant primary reads the narrow counts a worker writes (missing reason zero-filled)
```
$ strict_narrow remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_wide remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4911 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Test 5: expect-wide, tolerant primary reads a wide counts a worker write
```
$ tolerant_wide remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_wide remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4917 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Reviewed By: jaykorean

Differential Revision: D106321320

fbshipit-source-id: fc1bd66a4d11ac6bf38dff6aa214ebafab75f3d0
2026-07-09 10:37:35 -07:00
Josh Kang 053a9de468 Fix broken github codex review (#14923)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14923

Fix the GitHub Actions Codex review job by running generated review prompts through generic `codex exec` instead of the specialized `codex exec review` subcommand. The workflow already embeds PR metadata, reviewer instructions, and the diff in `/tmp/prompt.txt`, while `codex exec review --base` rejects a positional/stdin prompt. This removes the invalid `--base`/stdin combination from both automatic and manual Codex review paths while leaving `/codex-query` behavior unchanged.

Reviewed By: xingbowang

Differential Revision: D111135454

fbshipit-source-id: 16e86adda41d3e784c59466658cb769278c909c7
2026-07-09 09:55:44 -07:00
Jay Huh f32f747125 Address reverse MultiScan follow-up comments (#14922)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14922

- Preserve source compatibility for `Scan::Reset` by restoring the default `reverse` argument.
- Make `HasMoreScanRanges()` defensive for reverse prepared scans instead of asserting.
- Restore broader forward coverage in `MultiScanAsyncIOTest` while keeping reverse coverage on the representative async IO matrix.
- Let `--multiscan_reverse` drive reverse MultiScan stress coverage independently of `--test_backward_scan`.
- Add the reverse MultiScan public API release note.

Reviewed By: xingbowang

Differential Revision: D111127403

fbshipit-source-id: 1a17d0a36aaf66e8cbc51f445d0f85e145fd653c
2026-07-08 20:56:36 -07:00
generatedunixname1395027625275998 c3f6e4d625 Cache GetKeysEndOffset in BlockIter to speed up Next inner loop (#14917)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14917

Reviewed By: kunalspathak

Differential Revision: D110209012

fbshipit-source-id: 8ce3b94ea9617977d956f3856d92d50f2b547ddc
2026-07-08 14:03:21 -07:00
Jay Huh e1ea04d9ef Add reverse order support to MultiScan (#14876)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14876

Add reverse scan support to `MultiScanArgs`/`MultiScan` so callers can iterate bounded scan ranges in descending order. Plumb reverse scan state through `DBIter`, `BlockBasedTableIterator`, and `MultiScanIndexIterator`, including reverse prefetch windowing and async I/O support. Update `db_bench` `multiscanrandom` to honor `--reverse_iterator` and report rows scanned so reverse MultiScan and iterator baselines can be compared on row throughput.

Add `db_stress --multiscan_reverse` coverage so the prepared MultiScan path can be stressed with `SeekForPrev()`/`Prev()` against a control iterator.

Also fix the table child iterator path to accept reverse `SeekForPrev()` targets above a file's prepared ranges. `MergingIterator` and `LevelIterator` can legally seek every child to the current range limit; a child file may only have lower prepared ranges, so treating that as invalid caused intermittent `SeekForPrev target is outside prepared ranges` exceptions in the benchmark.

Reviewed By: xingbowang

Differential Revision: D109357730

fbshipit-source-id: eccece5ca7d8f5d6760d51ebe76801a332a49061
2026-07-08 13:58:00 -07:00
anand1976 27b48646cd Update folly hash for 11.6 release (#14912)
Summary:
- Update `folly.mk` FOLLY_COMMIT_HASH to 2a68075b77d9

This is folly's `main` at release-cut time. Review and merge to take the bump, or close to skip folly for this release.

Part of 11.6 release workflow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14912

Reviewed By: jaykorean

Differential Revision: D110524765

Pulled By: pdillinger

fbshipit-source-id: 3361fdc6277b5f02828f23942b9301237b1ed272
2026-07-07 18:44:23 -07:00
generatedunixname1395027625275998 66bccb467d Store BySmallestKey by value to avoid shared_ptr atomic copy in Rep (#14919)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14919

Reviewed By: DenisYaroshevskiy

Differential Revision: D110842287

fbshipit-source-id: df84b4b319aefd9b21abdb7db20989ba1efd7eba
2026-07-07 07:42:34 -07:00
generatedunixname1395027625275998 0f15da1c2d Reserve key_context capacity in MultiGetCommon to avoid autovector reallocs (#14909)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14909

Reviewed By: kunalspathak

Differential Revision: D110380492

fbshipit-source-id: d1d3214c52a65326de237adcbfe6ea5d23d94960
2026-07-06 19:48:44 -07:00
anand1976 13d55eba73 Update version to 11.7.0 for 11.6 release (#14913)
Summary:
- Bump version.h from 11.6.0 to 11.7.0
- Add `11.6.fb` to `check_format_compatible.sh`
- Sync HISTORY.md `## 11.6.` section from `11.6.fb`
- Delete 5 consumed `unreleased_history/` note file(s) from main

Part of 11.6 release workflow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14913

Reviewed By: anand1976

Differential Revision: D110520723

Pulled By: pdillinger

fbshipit-source-id: 20f2f9b4be03c08d78d6f2c4e6b70812a408d9f0
2026-07-02 16:16:22 -07:00
Peter Dillinger 1192897aad Fix table cache entry leak in VersionBuilder::UnrefFile on failed LogAndApply (#14914)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14914

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14720

### Context

`VersionSet::LogAndApply` loads table handlers before the `MANIFEST` update is durable. If that `MANIFEST` update later fails, newly added files can be discarded by `VersionBuilder` without ever being installed in a `Version`. `VersionBuilder::UnrefFile` released the `FileMetaData` `pinned_reader` handle, but releasing that handle only dropped the reference; it did not erase the cache key, so an orphaned table-cache entry could survive for a file that is neither live nor quarantined. When metadata read fault injection prevents the obsolete-file scan from cleaning up the orphan, DB close can trip `TEST_VerifyNoObsoleteFilesCached` with File N is not live nor quarantined.

### Fix
After releasing a pinned_reader for a `FileMetaData` whose refcount reaches zero, explicitly evict that file number from the table cache. The new `VersionBuilderDBTest.FailedLogAndApplyEvictsTableCacheEntry` regression test exercises the realistic production path: it creates a real SST, calls `LogAndApply` through the DB's own `VersionSet`, injects a MANIFEST sync failure via the `AfterSyncManifest` sync point, and asserts that the table cache no longer contains the file after `ProcessManifestWrites` destroys the `VersionBuilder`. With the eviction removed, the test fails deterministically with `leaked_cache_entry=true`.

Reviewed By: hx235

Differential Revision: D99759696

fbshipit-source-id: 0ebb32b1544ad1982d23a6c06569984be97a0a88
2026-07-02 16:08:21 -07:00
Peter Dillinger a40466d963 Three misc CI fixes (#14910)
Summary:
Fixes three unrelated nightly/CI failures.

Makefile: 'make build_folly' was tripping the build-signature change check, causing the folly-flavored jobs (e.g. nightly clang-21 ASAN+UBSAN+folly, which runs 'make build_folly' then 'USE_FOLLY=1 ... make check') to abort with 'Build parameters changed since the last build'. build_folly only builds the external folly library under third-party/folly and never compiles RocksDB objects into OBJ_DIR, so it should not record or check a build signature; add it to BUILD_SIG_NONBUILD_GOALS alongside checkout_folly.

util/rate_limiter_test.cc: RateLimiterTest.Rate's minimum-rate assertion is timing-sensitive and flakes on the small/loaded ARM CI runners, which (unlike x86_64) can't reliably sustain the target rate. Extend the existing CI exemption with an ARM-only GITHUB_ACTIONS branch, preserving the check on x86_64.

tools/ldb_test.py: the list_live_files_metadata output includes the full DB path, and the unescaped-dot regex '\d+.sst' could match a <digit><char>sst run inside the random tempfile.mkdtemp suffix (drawn from [a-z0-9_]) that precedes the real filename, collapsing testMap to a single bogus key such as '8asst'. Require a literal '.sst'; temp-dir characters can never contain a dot, so only the real filename matches.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14910

Test Plan:
- build_folly: confirmed 'make build_folly' no longer writes .build_signature and that BUILD_SIG_DO_BUILD resolves empty for that goal.
- rate_limiter_test: confirmed via the C preprocessor that x86_64 keeps the SANDCASTLE-only exemption while aarch64 selects the new GITHUB_ACTIONS branch.
- ldb_test: reproduced the collapse with a poisoned temp suffix (produces {'8asst': ['0', 'mycol2']}) and confirmed the escaped-dot regex yields the correct 4-entry map.

Reviewed By: mszeszko-meta

Differential Revision: D110405164

Pulled By: pdillinger

fbshipit-source-id: a9a73b7a717e726fdf79d7795f085f3dbede940e
2026-07-01 19:04:40 -07:00
Anand Ananthabhotla a0ad7887c9 Fix null scratch buffer submitted to async read on aligned direct-IO path (#14907)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14907

`RandomAccessFileReader::ReadAsync` took its "already aligned" fast path when the request offset and length were sector-aligned, forwarding the caller's `req` (including its `scratch` pointer) straight to the FileSystem. A null `scratch` trivially satisfied the alignment check (`uintptr_t(nullptr) & (alignment - 1) == 0`), so when a caller submitted an aligned request with `scratch == nullptr` and an `aligned_buf` out-parameter for the reader to allocate into, the null buffer was submitted to the async read. For direct IO with io_uring this becomes a null `iovec` base, which the kernel rejects with `EFAULT` (surfaced as `Req failed: Unknown error -14`).

This is exactly how `IODispatcher` submits MultiScan async reads for plain direct IO (`scratch == nullptr`, `aligned_buf` provided), so the failure manifested intermittently as `db_stress` iterator divergence ("Iterator diverged from control iterator") in crash tests with `use_direct_reads=1 --use_multiscan=1 --multiscan_use_async_io=1`. It is intermittent because it only triggers when the coalesced read's offset and length both happen to be sector-aligned. Task T267030385.

The fix excludes the aligned fast path when the caller provided no `scratch` but did provide an `aligned_buf`, forcing the allocating path so a valid backing buffer is always handed to the async read. This matches the existing behavior of the synchronous `Read`, which already guards its aligned fast path with `scratch != nullptr`.

Reviewed By: xingbowang

Differential Revision: D110361301

fbshipit-source-id: 3884f7ddbc746cf51b2566d74918e2fb97233dcb
2026-07-01 15:25:12 -07:00
Peter Dillinger 6cceef9497 Bug fixes: surface embedded-blob resolution errors (#14906)
Summary:
Experimental embedded ("same-file") blob SST support (https://github.com/facebook/rocksdb/issues/14851) overloads blob file number 0 as both kInvalidBlobFileNumber and the same-file sentinel kCurrentFileBlobIndexFileNumber. Same-file references must be resolved by EmbeddedBlobResolvingIterator before they reach any generic file-metadata or integrated-BlobDB path, all of which reject file number 0 as invalid.

When resolving a same-file blob hit an error (e.g. a db_stress-injected blob-region read fault), EmbeddedBlobResolvingIterator::value() fell back to returning the RAW, unresolved same-file value, surfacing the error only via status()/Valid(). Compaction consumed the raw value before consulting status, leaking an unresolved same-file reference into the output stream. Depending on the value type this produced two different crash-test failures, both from this one root cause:

* T277566778 -- wide-column entity variant. The raw same-file entity (kTypeWideColumnEntity) reached FileMetaData::UpdateBoundaries, whose blob-ref scan (correctly) rejected file number 0: Flush failed: Corruption: Invalid blob file number (also observed from a background compaction). The tripwire fired on the leak, so the real injected read error was masked behind a misleading corruption status.

* T277310719 -- whole-value BlobIndex variant, and more dangerous because it escapes that tripwire. ResolveKeyType() rewrites the key type kTypeBlobIndex -> kTypeValue with no I/O (so it always succeeds); on the masked error the emitted entry is therefore {kTypeValue, raw BlobIndex bytes}. UpdateBoundaries only scans kTypeBlobIndex/entity types, so it does NOT reject this record: the corruption is silently written to the compaction output and persists. A later point lookup reads those raw BlobIndex bytes back as the value, which db_stress detects as db_stress: expected_value.cc:102: Assertion `ExpectedValue::IsValueBaseValid(value_base)' failed from TestGet. In the captured crash the aborting db_stress process had injected no faults itself -- it read corruption persisted by an earlier faults-on run (blackbox reuses the DB directory), confirming the persistence route.

Fix: EmbeddedBlobResolvingIterator now resolves eagerly for callers that do not opt into unprepared values (allow_unprepared_value=false, e.g. compaction), via a new EagerEmbeddedBlobResolvingIterator (the caller is selected in BlockBasedTable::NewIterator). Eager callers resolve the value during positioning, so a resolution error (blob-region I/O or corruption) is observable through status()/Valid() BEFORE value() is consumed, and value() never exposes an unresolved same-file BlobIndex. Lazy callers (allow_unprepared_value=true, user iteration) keep value laziness but must honor PrepareValue()'s result. This keeps the "callers never see an unresolved same-file blob" invariant structural, even on the error path. Using a template for EmbeddedBlobResolvingIterator minimizes unnecessary overheads.

Also hardens and documents the integrity tripwires that caught the leak, so they are not "fixed" by weakening them -- doing so would mask real corruption and could persist unresolvable same-file references into ordinary (non-embedded) SSTs, turning a transient error into permanent data loss: FileMetaData::UpdateBoundaries (write/output path), Version::GetBlob and Version::MultiGetBlob (integrated-BlobDB read path), plus the contract note in blob_constants.h.

Adds a test-only sync point
"BlockBasedTable::MaybeResolveEmbeddedValue:InjectError" (compiled out under NDEBUG) to simulate a blob-region resolution fault deterministically.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14906

Test Plan:
New unit tests in db_blob_index_test, both of which fail without the fix and pass with it:

* DBBlobIndexTest.EmbeddedBlobResolveErrorDuringCompactionNotMasked -- entity variant. Without the fix CompactRange fails with the masked "Invalid blob file number"; with the fix it fails with the injected IOError.
* DBBlobIndexTest.EmbeddedBlobResolveErrorWholeValueDuringCompactionNotMasked -- whole-value variant. Without the fix CompactRange SUCCEEDS (silently masking the error and persisting {kTypeValue, raw BlobIndex}); with the fix it fails with the injected IOError.

Confirmed the tests exercise the real root cause by temporarily neutralizing the eager resolution (MaybeEagerlyMaterialize): both tests then fail in their respective pre-fix modes (entity -> "Invalid blob file number"; whole-value -> compaction succeeds), then pass again once restored.

End-to-end: the db_stress reproducer for T277566778 (blackbox crash test with embedded-blob ingestion and fault injection) reproduced the "Invalid blob file number" crash before the fix and no longer reproduces with it.

Reviewed By: anand1976

Differential Revision: D110361228

Pulled By: pdillinger

fbshipit-source-id: 1faaa9a9ead726f17b611ff150418fa59ed75fd4
2026-07-01 14:05:36 -07:00
generatedunixname1395027625275998 1f7a286413 Fix HyperClockCache naive Lookup fast-path chain indexing to skip full fallback (#14901)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14901

Reviewed By: anand1976

Differential Revision: D109906106

fbshipit-source-id: 660a47f51250415286af4f9ddcb0c40410ef04cd
2026-07-01 13:45:18 -07:00
Xingbo Wang de7b8e8f7d Add RocksDB flush reason and atomic flush request metrics (#14898)
Summary:
This PR adds flush observability for debugging write stalls and flush bottlenecks.

  Changes include:

  - Add flush reason tickers for physical flush jobs:
      - rocksdb.flush.reason.write_buffer_full
      - rocksdb.flush.reason.write_buffer_manager
      - rocksdb.flush.reason.memtable_max_range_deletions

  - Add flush memtable size histograms:
      - rocksdb.flush.memtable.memory.bytes
      - rocksdb.flush.memtable.total.data.size.bytes
      - rocksdb.flush.write_buffer_full.memtable.memory.bytes
      - rocksdb.flush.write_buffer_manager.memtable.memory.bytes

  - Add FlushReason::kMemtableMaxRangeDeletions and propagate it from memtables when memtable_max_range_deletions triggers a flush.
  - Preserve atomic flush as a single request-level reason. For atomic flush, the reason is derived only from CFs that scheduled the flush, not every CF selected into the atomic flush cut.
  - Add atomic flush request-level counters:
      - rocksdb.atomic_flush.request.reason.write_buffer_full
      - rocksdb.atomic_flush.request.reason.write_buffer_manager
      - rocksdb.atomic_flush.request.reason.memtable_max_range_deletions
      - rocksdb.atomic_flush.request.reason.other

  - Keep existing per-CF flush job reason counters unchanged. Under atomic flush, rocksdb.flush.reason.* still counts physical CF flush jobs, while rocksdb.atomic_flush.request.reason.* counts one logical atomic flush request.
  - Wire the new flush reason, ticker, and histogram types through Java and JNI bindings.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14898

Test Plan: CI

Reviewed By: anand1976

Differential Revision: D110214337

Pulled By: xingbowang

fbshipit-source-id: 4a445c4175d2e09ff5edbe66fde81fa00bb9e8da
2026-07-01 10:34:16 -07:00
Peter Dillinger 741539da9b Fix backward iteration over embedded blob SSTs (#14902)
Summary:
D110140135/https://github.com/facebook/rocksdb/issues/14896 worked around a db_stress failure by disabling test_backward_scan whenever embedded-blob SSTs are ingested. The real limitation was in EmbeddedBlobResolvingIterator: DBIter requires the underlying iterator's value to be pinned for backward iteration (Prev/SeekForPrev), and rebuilt wide-column entity values (with same-file blob columns) were stored in an iterator-owned std::string that was cleared on every reposition, so IsValuePinned() returned false and DBIter returned NotSupported (or tripped the IsValuePinned() assertion in FindValueForCurrentKeyUsingSeek). Whole-value same-file blobs were already pinned and worked.

This change makes the rebuilt wide-column value pinnable like a whole-value blob: it is moved into a heap buffer and pinned into resolved_pinned_value_, whose cleanup ResetState() hands to the PinnedIteratorsManager across repositioning so the value survives backward iteration. The now-redundant resolved_value_ member is removed and value()/IsValuePinned() simplified. The Get/MultiGet resolution path (MaybeResolveEmbeddedValue) is unchanged. The db_stress test_backward_scan workaround is removed now that backward iteration over embedded blobs (including wide-column entities) is supported.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14902

Test Plan:
Added DBBlobIndexTest.EmbeddedBlobBackwardIteration: ingests an embedded-blob SST with whole-value blobs plus a mixed wide-column entity (inline default column + embedded same-file blob column), then iterates backward via SeekToLast/Prev and SeekForPrev, verifying values and columns. Verified it fails before the fix (NotSupported at the entity) and passes after.

Full db_blob_index_test and sst_file_reader_test suites pass. make format-auto clean. Direct db_stress run with
ingest_external_file_with_embedded_blobs=1, use_put_entity_one_in=3, test_backward_scan=1, iterpercent>0 ran with no divergence/NotSupported/ assert.

Reviewed By: anand1976

Differential Revision: D110271453

Pulled By: pdillinger

fbshipit-source-id: 501a50d3c97bac9a217b604c43c8748abbb563c9
2026-07-01 10:02:52 -07:00
Hui Xiao 7e2223f4ab CPU corruption injector: randomize non-preset db_stress flags (#14867)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14867

Adds an opt-in --randomize_stress_flags mode to the CPU corruption injection runner.

The fixed DB_STRESS_PRESET keeps an injected corruption isolated and observable, but every run then exercises the same stress test flags (default values for non DB_STRESS_PRESET flags). To widen what a runner covers, we want every other db_stress knob to vary run to run.

Therefore this PR makes each run's db_stress flags are a fresh db_crashtest.py roll (its full blackbox parameter space) with DB_STRESS_PRESET pinned on top. A small CLOSURE gate set stops db_crashtest's finalize_and_sanitize() from un-pinning preset flags, and an assertion fails the run fast if any preset flag is changed. This is considered as a work-around until we can refactor the flag generation of db_crashtest.py into a isolated function to take a set of flags to honor.  This PR also adds some randomization into `max_keys` and local vs remote compaction as they are subjected to some special constraint unique to the cpu corruption injection framework.

The per-run roll is seeded by the run's seed, so randomized runs' flags are reproducible with --seed (though the actual db stress test behavior may still have some difference under the same flags).

**Test plan:**
Functional test
- build_runs(..., randomize=True) produces a full randomized flag set (~286 flags vs ~49 for the fixed preset) and the preset survives the db_crashtest roll (the _assert_preset_survived check never fires).
- randomized runs are reproducible: same base_seed -> same flags; run i seeded base_seed+i.

Integration test
```
for op in write flush compaction; do
    python3 tools/cpu_corruption_injector/runner.py --op $op --runs 30 --randomize_stress_flags --parallel 8 --stress_cmd "$BIN" --report_dir /tmp/icc-rand/$op
done
```
- every run still carries the pinned DB_STRESS_PRESET values (preset-survived assertion never fires across the campaign).
- the workload genuinely varies run to run (e.g. max_keys, different compaction_style / memtablerep / *_one_in values across runs), confirming randomization is active.
- SDC still appears across write / flush / compaction -- randomization widens coverage without losing the SDC signal; NO_INJECTION and ERROR stay ~0.

**Runs' Outcomes (`summary.json`):**

| SDC | CORRUPTION | CRASH | NO_EFFECT | NO_INJECTION | ERROR |
| --- | --- | --- | --- | --- | --- |
| 2 | 7 | 7 | 43 | 1 | 0 |

Local vs remote compaction. With `--randomize_stress_flags` a compaction run may flip to the remote compaction service (`remote_compaction_worker_threads=1`), so the injector breaks on `CompactionServiceCompactionJob::Run` instead of the local `CompactionJob::Run`. Splitting the runs by injection entry_fn:

| where | runs | SDC | CORRUPTION | CRASH | NO_EFFECT | NO_INJECTION |
| --- | --- | --- | --- | --- | --- | --- |
| local | 25 | 1 | 1 | 3 | 20 | 0 |
| remote | 35 | 1 | 6 | 4 | 23 | 1 |

Reviewed By: pdillinger

Differential Revision: D108551605

fbshipit-source-id: d3dc5687e70ab57779ad6e4f87cac7ed0086da34
2026-06-30 20:54:30 -07:00
Hui Xiao eddfee8f56 CPU corruption injector: runner + db_stress preset flags (#14866)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14866

Orchestration layer of the CPU corruption injector -- the glue between detection (https://github.com/facebook/rocksdb/pull/14852) and injection (https://github.com/facebook/rocksdb/pull/14858
).

One CPU-corruption injection says little on its own. What matters is the OUTCOME DISTRIBUTION over many injections -- how often a corruption is silently absorbed (NO_EFFECT), crashes the process (CRASH), is caught by an integrity check (CORRUPTION), or more importantly slips through as a silent data corruption (SDC) and the paths frequently leading to those outcomes. A trustworthy distribution needs a (somewhat) repeatable and reproducible harness as well as a db_stress configuration in which an injected corruption is both reachable and attributable to the chosen stress test op (i.e, write, foreground compaction or flush).

Therefore this PR implements a runner that can launch N independent runs for a chosen op type (i.e, write, foreground compaction or flush). Each run picks where to inject, runs db_stress under gdb via the `injector,py` (https://github.com/facebook/rocksdb/pull/14858), and is classified into one outcome bucket (https://github.com/facebook/rocksdb/pull/14852).

The runner has DB_STRESS_PRESET -- the pinned db_stress config that isolates a single injected corruption (single-threaded, integrity checks on, other fault injection off, auto-compaction off). The runner also does gdb preflight that fails fast when the build or gdb cannot support injection because, for example, the hard-coded `target_fn` has changed its name, provides parallel launching of many runs and one summary.json per campaign (the outcome distribution plus each run's record). The whole run set is reproducible from one logged base_seed (run i uses base_seed + i).

**Test plan:**

Build: `make DEBUG_LEVEL=0 EXTRA_CXXFLAGS="-g -fno-inline" db_stress`

# 1. Preflight (`verify_injection_site`) catches a build that can't support injection

Before doing any work, the runner has gdb confirm — on this exact binary — that every injection-site function resolves and gdb can read its source line. A good build logs:

```
INFO gdb check OK for op=compaction: rocksdb::CompactionJob::Run, rocksdb::CompactionIterator::NextFromInput, rocksdb::BlockBuilder::Add
```

A build that cannot support injection (functions renamed, fully inlined, or absent) fails fast with exit 2 before any run — forced here by pointing `--stress_cmd` at a non-db_stress binary:

```
$ python3 tools/cpu_corruption_injector/runner.py --op compaction --runs 1 --stress_cmd /bin/ls --report_dir /tmp/icc/preflight_demo
ERROR gdb could not set a breakpoint on these functions in ls (renamed, fully inlined, or not in this build?): rocksdb::CompactionJob::Run, rocksdb::CompactionIterator::NextFromInput, rocksdb::BlockBuilder::Add
Function "rocksdb::CompactionJob::Run" not defined.
Function "rocksdb::CompactionIterator::NextFromInput" not defined.
# exit code 2
```

So a broken/inlined build is rejected up front instead of silently producing `NO_INJECTION` runs.

# 2. Compaction op -- 100 runs
```
$ python3 tools/cpu_corruption_injector/runner.py --op compaction --runs 100 --stress_cmd /data/users/huixiao/rocksdb/db_stress  --report_dir /tmp/icc/preflight_demo
```
**Runs' outcomes (`summary.json`):**

| SDC | CORRUPTION | CRASH | NO_EFFECT | NO_INJECTION | ERROR |
| --- | --- | --- | --- | --- | --- |
| 9 | 5 | 6 | 79 | 1 | 0 |

**Spread:**
- target_fn x outcome: `NextFromInput` {SDC 9, CORRUPTION 3, CRASH 2, NO_EFFECT 34, NO_INJECTION 1}; `BlockBuilder::Add` {CORRUPTION 2, CRASH 4, NO_EFFECT 45}.
- corruption_type x outcome: `bit_flip` {SDC 8, CORRUPTION 3, CRASH 6, NO_EFFECT 32}; `flag_flip` {SDC 1, CORRUPTION 2, NO_EFFECT 42}; `lane_bit_flip` {NO_EFFECT 5}.

**Analysis:** all 9 SDCs land on the read/iterate path (`NextFromInput`); corrupting the output writer (`BlockBuilder::Add`) never produced an SDC (its blocks are checksummed -- corruption there is caught or inert). The 5 detected `CORRUPTION`s are compaction's key-order and record-count cross-checks firing (both CompactRange and CompactFiles origins appear), correctly bucketed by the fix.

### A representative compaction SDC: `run_00000`

What we corrupted (`inject.json`):

```json
{"op":"compaction","op_index":17,"entry_fn":"rocksdb::CompactionJob::Run","target_fn":"rocksdb::CompactionIterator::NextFromInput","injection_result":"injected","db_stress_crash_signal":null,
 "corruptions":[{"instruction":"mov    %rdx,0x160(%r12)","register":"rdx","corruption_type":"bit_flip","before":"0x10","after":"0x18",
   "details":{"source":"rocksdb::CompactionIterator::NextFromInput @ db/compaction/compaction_iterator.cc:719"}}]}
```

The recorded silent corruption (`data_corruption.<tid>.json`):

```json
{"kind":"wrong-value","cf":0,"key":70,"value_from_db":"010000000504070609080B0A0D0C0F0E070F105E78787878","value_from_expected":"010000000504070609080B0A0D0C0F0E","op_status":"Get: OK"}
```

**Walkthrough:** a single bit flip on `rdx` (`0x10 -> 0x18`, 16 -> 24) at `CompactionIterator::NextFromInput` (`compaction_iterator.cc:719`) -- `rdx` holds the value LENGTH stored into the iterator's record (offset `0x160`). The length reads 24 instead of 16, so compaction copies a value 8 bytes too long into the output SST, absorbing adjacent bytes. The internal key is untouched (`ParseInternalKey` passes); the over-long value is the single Slice fed to both the paranoid validator and the SST builder, so the file is self-consistent and every checksum agrees. On read-back `Get(key=70)` returns OK with the wrong bytes -- `value_from_db` is the expected value (`...0F0E`) **plus 8 trailing bytes** (`070F105E78787878`). Silent: read OK, all checks pass, the value visibly grew. `classify()` routes `kind=wrong-value` to the SDC bucket.

### A representative compaction CORRUPTION (detected): `run_00007`

What we corrupted (`inject.json`):

```json
{"op":"compaction","op_index":41,"entry_fn":"rocksdb::CompactionJob::Run","target_fn":"rocksdb::CompactionIterator::NextFromInput","injection_result":"injected","db_stress_crash_signal":"SIGABRT",
 "corruptions":[{"instruction":"mov    (%rbx),%rdi","register":"rdi","corruption_type":"bit_flip","before":"0x7fffeee8c1c0","after":"0x7fffeee8c1c2",
   "details":{"source":"rocksdb::IterKey::SetKeyImpl @ ./db/dbformat.h:941","call_chain":["rocksdb::IterKey::SetKeyImpl @ ./db/dbformat.h:941","rocksdb::CompactionIterator::NextFromInput @ db/compaction/compaction_iterator.cc:781"]}}]}
```

The recorded detection (`data_corruption.<tid>.json`):

```json
{"kind":"detected-corruption","cf":-1,"key":-1,"value_from_db":"","value_from_expected":"","op_status":"compactfiles: Corruption: Compaction sees out-of-order keys."}
```

**Walkthrough:** a bit flip on `rdi` (`...c1c0 -> ...c1c2`, a key pointer) at `IterKey::SetKeyImpl` (`dbformat.h:941`), reached from `NextFromInput`, mis-sets the iterator's key so the next emitted key is out of order. Compaction's key-order check catches it and returns `compactfiles: Corruption: Compaction sees out-of-order keys`. The op then takes `SIGABRT`, but `classify()` reads the recorded `data_corruption` result before the crash signal, so the run is correctly bucketed `CORRUPTION` (the bucketization fix; pre-fix this surfaced as `CRASH`). `classify()` routes `kind=detected-corruption` to the `CORRUPTION` bucket.

# 3. Flush op -- 100 runs
```
$ python3 tools/cpu_corruption_injector/runner.py --op flush --runs 100 --stress_cmd /data/users/huixiao/rocksdb/db_stress  --report_dir /tmp/icc/preflight_demo
```
**Runs' outcomes (`summary.json`):**

| SDC | CORRUPTION | CRASH | NO_EFFECT | NO_INJECTION | ERROR |
| --- | --- | --- | --- | --- | --- |
| 2 | 12 | 11 | 74 | 1 | 0 |

**Spread:**
- target_fn x outcome: `BlockBuilder::Add` {SDC 1, CORRUPTION 5, CRASH 5, NO_EFFECT 49}; `NextFromInput` {SDC 1, CORRUPTION 7, CRASH 6, NO_EFFECT 25, NO_INJECTION 1}.
- corruption_type x outcome: `bit_flip` {SDC 2, CORRUPTION 9, CRASH 9, NO_EFFECT 32}; `flag_flip` {CORRUPTION 3, CRASH 2, NO_EFFECT 42}.

**Analysis:** flush mirrors compaction's mechanisms (shared iterator/builder). The 2 SDCs are a value/key-pointer corruption that slips past the checksums; the 12 corruptions are caught by the flush-time key-order / key-size integrity checks.

### A representative flush SDC: `run_00027`

What we corrupted (`inject.json`):

```json
{"op":"flush","op_index":16,"entry_fn":"rocksdb::FlushJob::Run","target_fn":"rocksdb::BlockBuilder::Add","injection_result":"injected","db_stress_crash_signal":null,
 "corruptions":[{"instruction":"mov    (%rdi),%rax","register":"rax","corruption_type":"bit_flip","before":"0x7fffef059400","after":"0x7fffef059440",
   "details":{"source":"rocksdb::Slice::data @ ./include/rocksdb/slice.h:58","call_chain":["rocksdb::Slice::data @ ./include/rocksdb/slice.h:58","rocksdb::BlockBuilder::AddWithLastKeyImpl @ table/block_based/block_builder.cc:351"]}}]}
```

The recorded silent corruption (`data_corruption.<tid>.json`):

```json
{"kind":"lost","cf":0,"key":763,"value_from_db":"","value_from_expected":"010000000504070609080B0A0D0C0F0E","op_status":"Get: NotFound"}
```

**Walkthrough:** a bit flip on `rax` (a key/value data pointer, `...9400 -> ...9440`) at `Slice::data` (`slice.h:58`), reached from `BlockBuilder::AddWithLastKeyImpl` while the flush builds the output block, makes the builder read key bytes from the wrong address, so key 763's entry is written wrong and the key is dropped from the flushed SST. On read-back `Get(key=763)` returns `NotFound` for a committed key -- silent. `classify()` routes `kind=lost` to the SDC bucket.

### A representative flush CORRUPTION (detected): `run_00047`

What we corrupted (`inject.json`):

```json
{"op":"flush","op_index":7,"entry_fn":"rocksdb::FlushJob::Run","target_fn":"rocksdb::CompactionIterator::NextFromInput","injection_result":"injected","db_stress_crash_signal":"SIGABRT",
 "corruptions":[{"instruction":"cmp    $0x7,%rax","register":"eflags","corruption_type":"flag_flip","before":"0x216","after":"0x256",
   "details":{"source":"rocksdb::ParseInternalKey @ ./db/dbformat.h:523","call_chain":["rocksdb::ParseInternalKey @ ./db/dbformat.h:523","rocksdb::CompactionIterator::NextFromInput @ db/compaction/compaction_iterator.cc:731"]}}]}
```

The recorded detection (`data_corruption.<tid>.json`):

```json
{"kind":"detected-corruption","cf":-1,"key":-1,"value_from_db":"","value_from_expected":"","op_status":"flush: Corruption: Corrupted Key: Internal Key too small. Size=16. "}
```

**Walkthrough:** a flag flip (`eflags 0x216 -> 0x256`) on a `cmp $0x7,%rax` branch in `ParseInternalKey` (`dbformat.h:523`), reached from `NextFromInput`, makes the parser mis-judge the internal-key size, so the flush emits a malformed key and the key-size integrity check returns `flush: Corruption: Corrupted Key: Internal Key too small`. The op takes `SIGABRT`; `classify()` reads the recorded `data_corruption` before the signal and buckets `CORRUPTION` (bucketization fix). `classify()` routes `kind=detected-corruption` to the `CORRUPTION` bucket.

# 4. Write op (`MemTable::Add`) -- two key spaces
```
$ python3 tools/cpu_corruption_injector/runner.py --op write --runs 100 --stress_cmd /data/users/huixiao/rocksdb/db_stress  --report_dir /tmp/icc/preflight_demo
```

A write injection corrupts a single `MemTable::Add` (a Put/Delete/DeleteRange). The corruption is reachable and attributable, but whether it surfaces as a *silent* write SDC depends heavily on the key space. A silent write SDC needs the affected/mispositioned key to have other live versions to fall through to -- which only happens in a dense, multi-version memtable. We therefore run two write campaigns: the default `max_key=1000`, then a small `max_key=8`. The contrast is what motivates randomizing `max_key` (see PR https://github.com/facebook/rocksdb/pull/14867  for `--randomize_stress_flags`).

### 4a. Default `max_key=1000` -- 100 runs (no silent write SDC)

**Runs' outcomes (`summary.json`):**

| SDC | CORRUPTION | CRASH | NO_EFFECT | NO_INJECTION | ERROR |
| --- | --- | --- | --- | --- | --- |
| 0 | 31 | 13 | 56 | 0 | 0 |

With a 1000-key space almost every write touches a distinct key, so a corrupted entry has no older live version to mask it: a value/key byte flip is caught at write by the per-key checksum (`VerifyEncodedEntry` -> `CORRUPTION`), and a structural flip tends to crash (`CRASH`) rather than silently mis-read. `ERROR=0`, `NO_INJECTION=0`. No write op silently corrupted data -- every reachable corruption was caught or crashed.

### 4b. Small `max_key=8` -- 100 runs (surfaces 2 silent write SDCs)

**Runs' outcomes (`summary.json`):**

| SDC | CORRUPTION | CRASH | NO_EFFECT | NO_INJECTION | ERROR |
| --- | --- | --- | --- | --- | --- |
| 2 | 33 | 8 | 57 | 0 | 0 |

Shrinking the key space makes each key hold ~125 versions (`ops_per_thread` / `max_key`), so a misplaced entry can fall through to an older version of the *same* key and be returned silently -- the per-key checksum (bytes intact) and on-seek verify cannot see a pure link-position error.

### A representative write SDC: `run_00028` (skiplist misposition -> silent stale read, flush catches)

What we corrupted (`inject.json`):

```json
{"op":"write","op_index":317,"entry_fn":"rocksdb::MemTable::Add","target_fn":"rocksdb::MemTable::Add","injection_result":"injected","db_stress_crash_signal":null,
 "corruptions":[{"instruction":"cmp    %rbx,-0xb8(%rbp)","register":"eflags","corruption_type":"flag_flip","before":"0x216","after":"0x217",
   "details":{"source":"rocksdb::MemTable::Add @ db/memtable.cc:1319",
              "call_chain":["rocksdb::MemTable::Add @ db/memtable.cc:1319"]}}]}
```

The recorded silent corruption (`data_corruption.<tid>.json`):

```json
{"kind":"resurrected","cf":0,"key":1,"value_from_db":"110000001514171619181B1A1D1C1F1E0100030205040706","value_from_expected":"","op_status":"Get: OK"}
```

**Walkthrough:** a flag flip (CF, `eflags 0x216 -> 0x217`) on the `cmp` that produces `KeyIsAfterNode` inside `InlineSkipList::Insert` (`inlineskiplist.h:1253`; the `@ memtable.cc:1319` in the record is inlining line-drift) inverts the key comparison, so the Delete tombstone for key 1 is linked at the wrong position. The stored bytes and per-key checksum are intact, so neither the checksum nor on-seek verify sees anything wrong -- on read-back `Get(key=1)` returns OK with key 1's live value for a key that was Deleted (`kind=resurrected`, silent). A follow-up `Flush()` in unit test repro *does* catch it: the full-scan order check returns `Corruption: Out-of-order keys found in skiplist` -- caught only after the silent read, not during it.

### A representative write CORRUPTION (detected) `max_key=1000 or 8` : `run_00018`

Where `run_00028`'s pure link-position error is invisible to the per-key checksum, this run shows a byte-level corruption that the checksum *catches* at write time. What we corrupted (`inject.json`):

```json
{"op":"write","op_index":106,"entry_fn":"rocksdb::MemTable::Add","target_fn":"rocksdb::MemTable::Add","injection_result":"injected","db_stress_crash_signal":null,
 "corruptions":[{"instruction":"mov    %rsi,(%rdi)","register":"rsi","corruption_type":"bit_flip","before":"0x7fffeec2a21c","after":"0x7fffeec2a25c",
   "details":{"source":"rocksdb::Slice::Slice @ ./include/rocksdb/slice.h:39",
              "call_chain":["rocksdb::Slice::Slice @ ./include/rocksdb/slice.h:39","rocksdb::GetVarint32 @ ./util/coding.h:280","rocksdb::MemTable::VerifyEncodedEntry @ db/memtable.cc:1102","rocksdb::MemTable::Add @ db/memtable.cc:1189"]}}]}
```

The recorded detection (`data_corruption.<tid>.json`):

```json
{"kind":"detected-corruption","cf":-1,"key":-1,"value_from_db":"","value_from_expected":"","op_status":"put: Corruption: ProtectionInfo mismatch"}
```

**Walkthrough:** a bit flip on `rsi` (`0x7fffeec2a21c -> 0x7fffeec2a25c`) at `Slice::Slice` (`slice.h:39`) while `MemTable::Add` re-parses the just-encoded entry through `VerifyEncodedEntry` (`memtable.cc:1102`) corrupts the Slice the verifier reads, so the recomputed per-key protection info no longer matches and the put returns `Corruption: ProtectionInfo mismatch`.

Reviewed By: pdillinger

Differential Revision: D108367345

fbshipit-source-id: 250b069c8e9d63a2dc257f6de86f1d9040284dd6
2026-06-30 20:38:13 -07:00
Hui Xiao 3745f2e234 CPU corruption injector: gdb register flip into one db_stress op (#14858)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14858

This PR is the injection layer of the CPU corruption injector, runs inside gdb and randomly corrupts a register by bit flip in exactly one db_stress op (i.e, write, foreground compaction and flush) per stress test run. Detection layer is at db_stress (https://github.com/facebook/rocksdb/pull/14852); orchestration layer is coming up.

__How one run works__
- The orchestration layer, coming up, randomly picks which stress test `op` instance (so corruption can land at different points in the LSM shape journey) and which `target_fn` of that `op` (so to cap instructions to step under a reasonable limit; `injector.py` in this PR randomly picks which instruction within the `target_fn` to inject (so corruption can land at different points of a `target_fn`).
- Attach: gdb starts with injector.py's parameters passed via -iex and the db_stress command after --args, so db_stress runs unmodified. Example:
```
gdb --batch --nx \
  -iex "py import sys; sys.argv=['injector.py','--op','write','--op_index','42','--entry_fn','rocksdb::MemTable::Add','--target_fn','rocksdb::MemTable::Add','--corruptions_per_op','1','--seed','7','--dir','<rundir>']" \
  -x tools/cpu_corruption_injector/injector.py \
  --args <db_stress> --threads=1 --verify_cpu_corruption_dir=<rundir>  ...
```
- Navigate: The orchestration layer will pick op_index. `entry_fn` is called exactly once per stress test run's op so the op_index-th op is its op_index-th call. `injector_navigate.py` breaks on `entry_fn` and set a gdb ignore-count of op_index-1 to fast-forward to op_index-th one. It also breaks at the first `target_fn` within that `entry_fn`.

- Warm up: `injector_critical_instruction.py` will choose "critical instruction" (those that move key/value bytes with general-purpose or vector registers or set a branch flag) uniformly within the chosen `target_fn` by the orchestration layer. In order to do that, it needs to approximate how many such instructions within the `target_fn`. Hence we have this warm-up phase. It single-steps the instruction within the first encoutering of `target_fn` to count and draw the critical instruction index, then corrupt that index at a later call.

- Corrupt: on a later call of `target_fn`, `injector_critical_instruction.py` single-step to the m-th critical instruction and bit-flip the register through `injector_register_corruption.py`. The way to corrupt register depends on what instruction it is. If the current call of `target_fn`'s m-th instruction is not a critical instruction, we will try next `target_fn` till running out of `target_fn`.

- Record: `injector_telemetry.py` provides telemetry to capture the corruption for later analysis.

**Test plan:**
1. Isolated tests (real gdb-captured x/i fixtures): test_inject_critical_instruction

2. E2E test on navigation, inject, telemetry will be done in the later orchestration PR. Below is inject.json from such run
```
{
  "injection_result": "injected",
  "db_stress_crash_signal": null,
  "op": "write",
  "op_index": 279,
  "entry_fn": "rocksdb::MemTable::Add",
  "target_fn": "rocksdb::MemTable::Add",
  "critical_instruction_index": 37,
  "corruptions": [
    {
      "instruction": "mov    %rsi,0x8c8(%rbx)",
      "register": "rsi",
      "corruption_type": "bit_flip",
      "before": "0x7fffee4c64d8",
      "after": "0x7fffee4c64c8",
      "details": {
        "source": "rocksdb::Arena::AllocateAligned @ ./fbcode/internal_repo_rocksdb/repo/memory/arena.cc:135",
        "call_chain": [
          "rocksdb::Arena::AllocateAligned @ ./fbcode/internal_repo_rocksdb/repo/memory/arena.cc:135",
          "rocksdb::ConcurrentArena::AllocateAligned(unsigned long, unsigned long, rocksdb::Logger*)::{lambda()#1}::operator()() const @ fbcode/internal_repo_rocksdb/repo/memory/concurrent_arena.h:65",
          "rocksdb::ConcurrentArena::AllocateImpl<rocksdb::ConcurrentArena::AllocateAligned(unsigned long, unsigned long, rocksdb::Logger*)::{lambda()#1}>(unsigned long, bool, rocksdb::ConcurrentArena::AllocateAligned(unsigned long, unsigned long, rocksdb::Logger*)::{lambda()#1} const&) @ fbcode/internal_repo_rocksdb/repo/memory/concurrent_arena.h:145",
          "rocksdb::ConcurrentArena::AllocateAligned @ fbcode/internal_repo_rocksdb/repo/memory/concurrent_arena.h:63",
          "rocksdb::InlineSkipList<rocksdb::MemTableRep::KeyComparator const&>::AllocateNode @ fbcode/internal_repo_rocksdb/repo/memtable/inlineskiplist.h:868",
          "rocksdb::InlineSkipList<rocksdb::MemTableRep::KeyComparator const&>::AllocateKey @ fbcode/internal_repo_rocksdb/repo/memtable/inlineskiplist.h:855",
          "rocksdb::(anonymous namespace)::SkipListRep::Allocate @ ./fbcode/internal_repo_rocksdb/repo/memtable/skiplistrep.cc:36",
          "rocksdb::MemTable::Add @ ./fbcode/internal_repo_rocksdb/repo/db/memtable.cc:1157"
        ]
      }
    }
  ],
  "ops_seen": 279,
  "critical_instructions_seen": 38
}
```

Reviewed By: pdillinger

Differential Revision: D107999835

fbshipit-source-id: 0863715e5d596d69abff8b8e4d9a252fa8c24b6c
2026-06-30 19:34:21 -07:00
Hui Xiao 0f493506d9 Verify CPU corruption after op via --verify_cpu_corruption_dir (#14852)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14852

Detection layer of the CPU corruption injector (https://github.com/facebook/rocksdb/pull/14858). With `--verify_cpu_corruption_dir=<dir>`, db_stress reads back the full keyspace after every write/manual flush/manual compaction op and compares it to the expected-values model, classifying any mismatch by `kind`: `lost` / `resurrected` / `wrong-value` (silent data corruption) or `detected-corruption` (a status/checksum-caught error). Each finding is written to `<dir>/data_corruption.<tid>.json` ({kind, cf, key, value_from_db, value_from_expected, op_status}) and routed through db_stress's standard `VerificationAbort` for a clean exit-1. A startup guard requires `--threads=1` and all fault injection off so the read-back is single-writer and the only corruption present is the injected one

Bonus: a minor refactoring into the surrounding error handling code in these ops

**Test plan:**

1.Startup guard rejects misconfiguration:
```
--threads=2           -> exit 1: "--verify_cpu_corruption_dir requires --threads=1"
--read_fault_one_in=5 -> exit 1: "requires all fault injection off"
```
2.No false positive (clean CORE preset run, no injection):
```
$ db_stress --verify_cpu_corruption_dir=<dir> --threads=1 (full protections, all *_fault_one_in=0) ...
exit 0; no data_corruption.<tid>.json produced; "Verification successful"
```
3.Write-path cpu corruption injection (coming up, e.g, gdb flips a register inside MemTable::Add), then the immediate post-op read-back catches it. Real `<dir>/data_corruption.<tid>.json`:

silent data corruption -- write returned OK but the key is gone on read-back:
```
{"kind":"lost","cf":0,"key":9814,"value_from_db":"","value_from_expected":"010000000504070609080B0A0D0C0F0E","op_status":"Get: NotFound"}
```
detected corruption -- read-back Get returns Corruption via the memtable per-key checksum:
```
{"kind":"detected-corruption","cf":0,"key":139,"value_from_db":"","value_from_expected":"","op_status":"Get: Corruption: Corrupted memtable entry, per key-value checksum verification failed."
```

4.See PR https://github.com/facebook/rocksdb/pull/14866 test plan's spread in the outcome for verification of detection

Reviewed By: pdillinger

Differential Revision: D107999834

fbshipit-source-id: 18decbf51dc56eec62b735136e2dfb4e1175b773
2026-06-30 18:54:53 -07:00
Anand Ananthabhotla 779c7b58d1 Disable db_stress backward scan with embedded blob SSTs (#14896)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14896

Replace the earlier mitigation that disabled test_backward_scan when
separate_key_value_in_data_block is set (D109837793). That gate was incorrect:
separate_key_value_in_data_block does not affect value pinning -- it was only
coincidentally correlated with the failing crash-test runs (the crash test
biases it on ~2/3 of the time).

Root cause: embedded blob SSTs are read through EmbeddedBlobResolvingIterator,
which resolves same-file blob references (and wide-column entities with same-file
blob columns) into an iterator-owned buffer. Those resolved values are not
pinnable, but DBIter requires pinned values for backward iteration
(Prev/SeekForPrev), returning NotSupported (surfaced as "Iterator diverged from
control iterator") or hitting the IsValuePinned() assertion in
FindValueForCurrentKeyUsingSeek (db_iter.cc).

Gate test_backward_scan on ingest_external_file_with_embedded_blobs instead.
This is a stress-test mitigation only; the underlying limitation (backward
iteration over unpinnable resolved values) should be fixed separately by making
the resolved value pinnable via the PinnedIteratorsManager.

___

Differential Revision: D110140135

fbshipit-source-id: 5288dab67ab753cbba22806e09fb8fec1e6f5a01
2026-06-29 22:46:48 -07:00
Anand Ananthabhotla ffb7788d45 Disable backward scan in stress test when separate_key_value_in_data_block is enabled (#14889)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14889

When `separate_key_value_in_data_block=true`, the data block iterator stores keys and values in separate sections. Under certain conditions during backward iteration (Prev/SeekForPrev), `DBIter::FindValueForCurrentKey` encounters entries whose values cannot be pinned, causing a `NotSupported` error: "Backward iteration not supported if underlying iterator's value cannot be pinned." This manifests in the crash test as `TestIterateAgainstExpected` failures when the random flag combination includes `separate_key_value_in_data_block=1` (added in 47cfeb656ff3) with `test_backward_scan=1` (the default). The fix automatically disables `test_backward_scan` when `separate_key_value_in_data_block` is enabled, matching the pattern used for other option incompatibilities in the stress tool.

Reviewed By: xingbowang

Differential Revision: D109837793

fbshipit-source-id: 04230e6c0af42e98fb82d0ddcc3d857419e879e3
2026-06-29 17:55:04 -07:00
Xingbo Wang 75c1ffd4e3 Add RocksDB rate limiter I/O usage metrics (#14884)
Summary:
Add rate limiter statistics that make it easier to diagnose whether RocksDB background IO is being throttled by the GenericRateLimiter.

New ticker counters:
- rocksdb.rate.limiter.bytes.read/write: bytes granted by the limiter, split by operation type.
- rocksdb.rate.limiter.requests.read/write: requests granted by the limiter, split by operation type.
- rocksdb.rate.limiter.delayed.requests.read/write: requests that actually had to wait for a future refill because available tokens were exhausted.
- rocksdb.rate.limiter.total.wait.micros.read/write: cumulative wait time for delayed requests.

New histograms:
- rocksdb.rate.limiter.wait.micros.read/write: per-request wait latency for delayed requests, enabling percentile debugging.

GenericRateLimiter now overrides the OpType-aware Request() path so these stats preserve the configured limiter mode. The legacy three-argument Request() path is treated as write IO for compatibility. Existing NUMBER_RATE_LIMITER_DRAINS is left intact, but the new delayed request counters are the clearer signal for out-of-token throttling because one delayed request can observe multiple drain intervals.

Also update the Java ticker and histogram mappings so Java consumers can access the new statistics.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14884

Test Plan:
- make format-auto
- make -j16 rate_limiter_test statistics_test
- ./rate_limiter_test
- ./statistics_test
- make check-sources

Reviewed By: anand1976

Differential Revision: D109694030

Pulled By: xingbowang

fbshipit-source-id: e92756788ba629f25655958c7beb91099184cda4
2026-06-29 16:22:57 -07:00
Peter Dillinger f6fabdb64f Improve build and test experience for Make builds (#14883)
Summary:
Local Make builds silently reuse object files even when build parameters (DEBUG_LEVEL, sanitizers, ASSERT_STATUS_CHECKED, RTTI, etc.) change, since all object files share the same paths. This leads to confusing linker errors, sanitizer false negatives, ODR violations, and "phantom" bugs that are easy to miss -- a pitfall for humans and especially for AI agents driving builds non-interactively.

This change improves the Make experience on three fronts:

1. Build-parameter change detection in the Makefile. After flags are fully resolved, we hash the effective compile/link inputs (CC|CXX|CFLAGS|CXXFLAGS|LDFLAGS|EXEC_LDFLAGS) into a per-OBJ_DIR .build_signature stamp and compare against the previous build:
   - Default: stop with a clear error when parameters changed. This is optimized for the expert who might not intend a full rebuild, and for avoiding CI inefficiency.
   - AUTO_CLEAN=1: automatically run clean-rocks and proceed.
   - ALLOW_BUILD_PARAMETER_CHANGE=1: skip the check (e.g. intentionally mixing DEBUG_LEVEL=1 and DEBUG_LEVEL=2 objects).
The check is skipped for dry runs (-n) and for non-building / self-cleaning targets (clean*, format, check-*, tags, gen-pc, check-progress, watch-log, release, coverage, the asan_*/ubsan_* variants, etc.). make_config.mk is removed only by the top-level `clean` target, not by clean-rocks, so the auto-clean does not delete the make_config.mk already included by the running make. The signature is removed last in clean-rocks so an interrupted clean keeps change detection meaningful.

3. New build_tools/rockstest.sh and build_tools/rocksptest.sh helpers that build and run unit tests in one step. They set AUTO_CLEAN=1 and build with -j<NCORES> (computed like the Makefile). rocksptest.sh runs one or more binaries under the checked-in gtest-parallel (sharing one parallel worker pool); rockstest.sh runs a single binary directly for a small number of cases. Both reject a leading-option first argument; `rockstest.sh install` writes ~/bin/rockstest and ~/bin/rocksptest shims that defer to the in-tree scripts (with a friendly message when run outside a source root). This is a big win for AI-agent workflows: one command instead of a separate `make` then test-run invocation, removing the overhead of monitoring two long commands, while avoiding serial builds and serial test execution.

4. CLAUDE.md guidance updated to recommend AUTO_CLEAN=1 for manual make invocations and the rocks(p)test.sh helpers for running tests

Although there might be drive to consolidate around the BUCK build for internal use, I'll note that last I checked it was around ~20 minutes to run all the unit tests under buck and ~2 minutes under `make check`.

Bonus: the first revision of this had copyright/license mistakes, and this change corrects similar errors elsewhere, with CLAUDE.md advice updated.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14883

Test Plan:
Manual verification of the Makefile change detection:
- Confirmed the resolved-flags hash is deterministic across runs for the same goal, and differs between configs (e.g. default/shared vs static_lib, and with/without ASSERT_STATUS_CHECKED).
- Unchanged parameters: rebuild does not error.
- Changed parameters: errors by default; AUTO_CLEAN=1 runs clean-rocks and proceeds; ALLOW_BUILD_PARAMETER_CHANGE=1 bypasses the check.
- Dry runs (make -n) and exempt targets (e.g. `make check-progress`, list_all_tests, gen-pc) do not trip the check and leave the stamp untouched.
- Reproduced the ASSERT_STATUS_CHECKED=1 auto-clean path end to end and confirmed it no longer fails with "No rule to make target 'make_config.mk'"; verified `make clean` removes make_config.mk while `clean-rocks` does not, and that the signature is deleted as the final clean-rocks step.

Manual verification of the helper scripts:
- shellcheck-clean and `bash -n` pass for both scripts.
- Usage/guard behavior: missing argument and leading-option first argument are rejected with a clear message.
- `rockstest.sh install` (validated against a temp HOME) generates both shims; running a shim outside a source root prints "(Not in a rocksdb source root directory?)" and exits non-zero.
- rocksptest.sh argument splitting verified for single binary, multiple binaries, and binaries followed by gtest-parallel/test args.
- .gitignore patterns verified with `git check-ignore` for .build_signature (root and jl/jls) and build_tools/__pycache__/.

Reviewed By: xingbowang

Differential Revision: D109713512

Pulled By: pdillinger

fbshipit-source-id: a801d29afa91f53a2dce717db9fe5e5d485cc154
2026-06-29 14:39:13 -07:00
Anand Ananthabhotla 8fae7ff39d Fix use-after-free in EmbeddedBlobResolvingIterator when key() called before value()
Summary:
### Embedded Blob Resolving Iterator Fix

#### Use-After-Free Bug Fix

This diff fixes a use-after-free bug in the `EmbeddedBlobResolvingIterator` class. The bug occurred when the `key()` method was called before the `value()` method, causing the `resolved_internal_key_` buffer to be freed prematurely.

#### Changes

The fix involves modifying the `EmbeddedBlobResolvingIterator` class to ensure that the `resolved_key` is not moved into `resolved_internal_key_` if it has already been resolved (`key_resolved_` flag is set to `true`). This prevents the `resolved_internal_key_` buffer from being overwritten and freed when `MaterializeValue()` is called.

Reviewed By: xingbowang

Differential Revision: D109796428

fbshipit-source-id: d89fb36428ba62e8d0be53c2acfa3cdb9bda76f4
2026-06-29 10:27:39 -07:00
Jay Huh 43d60bfe9e Revert reverse-comparator handling in range tree lock manager (#14887)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14887

This reverts two changes to the range-lock endpoint comparator and restores the prior, direction-agnostic behavior:
- https://github.com/facebook/rocksdb/pull/14831 ("Fix range lock manager crash with reverse comparator"), which made RangeTreeLockManager::CompareDbtEndpoints reverse-aware, and
- https://github.com/facebook/rocksdb/pull/14880 (a follow-up that only corrected the equal-length / point-range branch).

Why: #14831 taught the endpoint comparator about reverse-ordered column families by flipping the suffix tie-break based on comparator direction. However, callers that use range locking on a reverse-ordered CF already flip the start/end endpoints themselves before calling lock_range() -- for example, MyRocks does this in ha_rocksdb::set_range_lock() (see its "RangeFlagsShouldBeFlippedForRevCF" comment). The two corrections stack and produce left > right again, which:
(a) trips paranoid_invariant(compare(left, right) <= 0) in toku::locktree::try_acquire_lock() and aborts on equality/point locks, and
(b) for range scans, mis-orders the endpoints and produces an incorrect lock range, leading to silent correctness issues under concurrency.
#14880 only addressed (a) (the equal-length / point-range branch), not (b), so it was an incomplete fix at the wrong layer.

Decision: keep CompareDbtEndpoints direction-agnostic and make it an explicit caller requirement to pass a flipped (swapped start/end, with negated infimum/supremum flags) range for range locking to work on a reverse-ordered column family. A function-level comment documenting this contract is added so reverse-awareness is not reintroduced here.

Removes the RangeLockWithReverseComparator unit test added by #14831 (it asserts the reverted direction-flipped ordering). Keeps a regression test that a point range lock [infimum(K), supremum(K)] works on a reverse-comparator column family.

Reviewed By: xingbowang

Differential Revision: D109713989

fbshipit-source-id: 4ea0999c92645e1adc9cd1114d924cad142f2001
2026-06-25 14:16:51 -07:00
Peter Dillinger 8c0790bdf8 Refresh platform010 build dependencies and remove TBB (#14882)
Summary:
Refresh the fbcode platform010 toolchain and library pins to current third-party2 versions, and remove the now-unused Intel TBB dependency.

Toolchain / deps:
- Fix update_dependencies.sh: the gcc and binutils trees moved from centos8-native to centos9-native, so the old paths resolved to nothing and a re-run emitted empty GCC_BASE/BINUTILS_BASE. Point them at centos9-native.
- Bump clang 15 -> 21; binutils 2.37 -> 2.43; libunwind 1.4 -> 1.8; valgrind 3.19 -> 3.22; plus hash-only refreshes for the LATEST-tracked libs. Regenerate dependencies_platform010.sh.
- Keep GCC pinned at 11.x. The only newer GCC in third-party2 (13.x) is built for glibc >= 2.35 (its libgcc_s needs _dl_find_object@GLIBC_2.35), but platform010 ships glibc 2.34, so GCC 13 will not link/run here.
- zlib stays 1.2.8 (the only version with an x86_64 platform010 build).
- Document why GCC/libgcc/glibc/zlib remain pinned in update_dependencies.sh.

Remove TBB (no longer used) from the build system: the get_lib_base entry, both fbcode_config*.sh, build_detect_platform detection, the CMake WITH_TBB option / find_dependency, and cmake/modules/FindTBB.cmake. Dockerfiles and the HISTORY.md changelog are left untouched. (TBB was used by the old clock cache, long ago removed.)

Although this change was originally motivated by upgrading gcc for its libasan not to hit process lifetime thread limits, upgrading gcc proved impractical under platform010.

Bonus: fix USBAN+gcc build by making GetParam() valid by the time it is called in several test class constructors.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14882

Test Plan:
a variety of local builds (gcc, clang; various sanitizers) using fbcode tooling

No production code changes

Reviewed By: xingbowang

Differential Revision: D109631787

Pulled By: pdillinger

fbshipit-source-id: 9c466c59b039c2a67ee0318c0ccbac02e19f537b
2026-06-25 10:05:45 -07:00
Peter Dillinger a004c2d850 Add experimental embedded blob SST support (#14851)
Summary:
Add EXPERIMENTAL embedded blob SST support for SstFileWriter through
OpenWithEmbeddedBlobs(). Eligible large values are written as same-file blob
records inline in a block-based SST as values are added (interleaved with data
blocks), while table entries store same-file BlobIndex references that readers
resolve for Get, MultiGet, and iteration, including mixed embedded and
non-embedded wide-column values.

Embedded-blob handling is folded directly into BlockBasedTableBuilder rather than
living in SstFileWriter or a separate table-builder wrapper: SstFileWriter only
selects the mode (via TableBuilderOptions::embedded_blob_options), and the
builder writes blob records inline using its own file writer and running offset.
This is enabled by disabling index-value delta encoding for these SSTs — delta
encoding reconstructs a data block's offset from the previous block and so
requires byte-contiguous data blocks, which interleaved blob records would break.
With full (non-delta) index values, blob records can sit between data blocks, so
no entry buffering/replay is needed. To keep inline blob appends correctly
ordered with data-block writes, these SSTs use single-threaded (non-parallel)
compression. The mode is the only entry point today but the placement keeps it
open to generalization beyond SstFileWriter; regardless, this experimental
feature is expected only to have niche applications.

(Previous revisions of this change allowed delta-encoded index blocks by
putting all blobs at the beginning of the file, but that was a more awkward
and memory-hungry implementation due to buffering all the data blocks before
writing.)

The on-disk record format (SimpleGen2Blob: payload bytes followed by a 5-byte
trailer of a compression marker plus a builtin checksum that is context-modified
by the record's absolute file offset) lives in db/blob/blob_gen2_format.{h,cc},
which now owns both the read (ReadAndVerifySimpleGen2BlobRecord) and write
(WriteSimpleGen2BlobRecord) sides so the format is defined in one place. This is
expected to be reused for upcoming "blog file" support.

Readers need no record-layout metadata: same-file blob resolution is purely
absolute-offset keyed, and the per-record offset-modified checksum (plus a cheap
"record fits within the file" bound) is the corruption guard. The reader's only
embedded-blob metadata is presence: a best-effort auxiliary table property
(blob count and payload-byte totals, for diagnostics) whose mere presence signals
that the SST contains embedded blobs.

Reads route through the column family's BlobSource when a DB is attached, so
embedded payloads are served from / inserted into the blob value cache and
recorded in BLOB_DB_* statistics; the cache key is derived from the
SimpleGen2Blob offset scheme (the same scheme block-based SST blocks use), so
embedded blob records stay collision-free with data blocks even when the blob
cache and block cache are the same cache. Non-DB openers (SstFileReader,
sst_dump, repair, ingestion prevalidation) have no BlobSource and fall back to a
direct, uncached read.

Same-file BlobIndex references use blob file number 0 as the marker. That value
also serves as the invalid blob-file-number sentinel in broader metadata code,
but the meanings do not conflict when used carefully: only the embedded-SST
reader/writer path interprets 0 as same-file, while generic file-metadata paths
continue to reject it as invalid. Using 1 would be worse because legacy
"stackable" BlobDB can use low blob file numbers, including 1, so reserving it
would collide with real blob files.

Compression options remain in the public API as placeholders, but embedded blob
compression support is deferred. Integrating compression with
BlockBasedTableBuilder while avoiding copied CompressAndVerifyBlock-style logic
is tricky enough to deserve a separate, focused PR.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14851

Test Plan:
- Added basic feature coverage to the crash test.
- Added BlobIndexTest.SameFileBlobIndex and BlobGarbageMeterTest.SameFileBlobIndex
  coverage for same-file BlobIndex encoding, display, recognition, and ignoring
  same-file references in blob-garbage accounting.
- Extended FileMetaDataTest.UpdateBoundariesBlobIndex to preserve the generic
  zero-file-number corruption check while keeping same-file embedded blob
  semantics at the table-reader/writer layers.
- SstFileReader embedded blob coverage: round-trip Get, MultiGet, and iterator
  reads; format_version gating; ignored placeholder compression options; the
  2048-byte default min_blob_size; wide-column mixed embedded/non-embedded
  values; and early append-error surfacing.
- Added an interleaved-layout test (small block_size with alternating
  small/large values) asserting the SST property index_value_is_delta_encoded==0,
  more than one data block, and that blob records are interspersed with data
  blocks (not a strict front prefix), with all values read back correctly via
  Get and iteration; replaces the old "ignored bytes before/after the blob record
  prefix" test.
- Added an embedded-record corruption test: flipping a byte inside a blob
  record's payload yields Corruption on read with verify_checksums (the
  offset-keyed record checksum is the guard now that the range pre-check is gone).
- Exercised the cached read path through BlobSource, including blob-cache
  hit/miss behavior and the shared blob_cache == block_cache configuration, in
  db_blob_index_test.
- Normal-path CPU regression check: release (DEBUG_LEVEL=0) db_bench on a
  non-embedded DB comparing this change vs upstream main, 3 interleaved reps of
  fillseq, fillrandom, readrandom, and readseq (5M keys, value_size=100,
  compression none, DB on /dev/shm). All deltas were within run-to-run noise
  (~1%), i.e. no measurable regression from adding the embedded-mode branch to
  the builder hot path.

Reviewed By: xingbowang

Differential Revision: D108564468

Pulled By: pdillinger

fbshipit-source-id: 5f01ffb1d40c6fd5b82d2451ec3342abb5040ca6
2026-06-25 09:29:50 -07:00
Xingbo Wang fe053a26aa Macos CI fix for jdk dependency issue (#14885)
Summary:
- Current 3rd party jdk8 installation fails due to trust issue in homebrew.
- Detect and reuse an existing Jdk installation on macOS runners before installing a new cask.
- Still target on jdk 8 binary build.
- Stop using 3rd party jdk installation,

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14885

Test Plan: - CI

Reviewed By: jaykorean

Differential Revision: D109696828

Pulled By: xingbowang

fbshipit-source-id: cc71958a05bd58acaac5f05fe3349ffd3e57880f
2026-06-25 08:38:32 -07:00
Josh Kang 8ba4204b28 Do not allow read only DBs to delete obsolete files (#14881)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14881

Read-only DB instances can share a directory with a live writer, so their view of live files is only a snapshot from the time they opened. After read-only DB open began setting `opened_successfully_` for its intended open-success semantics, that state had an unintended side effect: the common close path treated a successful read-only open like a read-write open and ran obsolete-file cleanup. If the writer created or made files live after the read-only handle opened, that cleanup could use the stale read-only live set and delete files still needed by the writer.

This change records whether a `DBImpl` was opened read-only and skips close-time obsolete-file cleanup for read-only DBs instead of simply keeping `opened_successfully_=false`, which could be confusing and easily mistaken in the future again.

For completeness I also updated other callsites of `opened_successfully_`, but those should not be real bugs as ReadOnly DBs do not run flushes/compactions or write to WALs.

Reviewed By: xingbowang

Differential Revision: D109622445

fbshipit-source-id: d7be4b20fce86ccb218a63ac6f5b707b316aac79
2026-06-24 16:04:52 -07:00
Jay Huh b0ecf86f6e Fix range lock manager assertion crash on reverse-comparator CF point locks (#14880)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14880

The RocksDB 11.5.0 reverse-comparator range-lock fix (the HISTORY.md entry "Fixed a bug where the range lock manager would crash with an assertion failure when using a reverse comparator column family") made `RangeTreeLockManager::CompareDbtEndpoints` reverse-aware by wrapping the endpoint-suffix tie-break results in an `is_reverse` flip. The length-disparity (different user key) branches need that flip, but the equal-length / equal-user-key branch was flipped too, and that is incorrect.

Range-lock endpoints are encoded as `[suffix_byte][user_key]` where the suffix is `SUFFIX_INFIMUM` (0x0) or `SUFFIX_SUPREMUM` (0x1). When two endpoints share the same user key and differ only in the suffix byte, the suffix is a positional boundary marker for that key, not key content, so the infimum endpoint must sort before the supremum endpoint in any total order, regardless of the column family sort direction. Flipping it for a reverse comparator makes `m_cmp(infimum(K), supremum(K)) > 0`.

MyRocks issues an equality lock (e.g. `UPDATE t1 SET a=123 WHERE a=35`) as the range `[infimum(K), supremum(K)]` over the same user key K. On a reverse (`rev:`) column family — which uses the stock `ReverseBytewiseComparator()`, so `is_reverse` is true — the flipped comparator reports `left > right`, tripping `paranoid_invariant(m_cmp(left_key, right_key) <= 0)` in `toku::locktree::try_acquire_lock` and aborting the server with SIGABRT. This broke many MyRocks `---range_locking` MTR tests (partial_index*, bypass_select_range_sk*, alter_table_online_debug, allow_no_primary_key_with_sk) after RocksDB was bumped to 11.5.0 in D108936924.

Fix: in the equal-length / equal-user-key branch of `CompareDbtEndpoints`, compare the suffix bytes without the `is_reverse` flip so infimum always precedes supremum. The length-disparity branches keep the reverse-aware flip (still covered by `RangeLockWithReverseComparator`).

Note: this lands in `internal_repo_rocksdb/repo`, the RocksDB development tree; it reaches `fbcode/rocksdb/src` (what MyRocks/ZippyDB link) through the RocksDB release sync. Tracked in T276980728.

Reviewed By: xingbowang

Differential Revision: D109591908

fbshipit-source-id: 078b16e6e51f4f365b4a8d539ac5b968a7461729
2026-06-24 13:46:52 -07:00
zaidoon 1cec28d82d Finish C API code generation (continues #14572) (#14868)
Summary:
This continues and finishes **https://github.com/facebook/rocksdb/issues/14572** ("Add semi-automated code generation for RocksDB C API bindings") by xingbowang. The original author is unavailable to finish it, so I've taken it over. **All 13 of the original commits are preserved** (this branch was created from the PR head and builds on top of it — `git log` shows the original `Xingbo Wang` authorship intact); my follow-up work is in the commits prefixed `C API codegen:`.

The underlying design is unchanged and is the original author's: hand-written source templates (`tools/c_api_gen/c_base.h` / `c_base.cc`) plus two generators (auto-discovery from the C++ headers + a spec-driven generator) are inlined into a single, self-contained, `generated` `include/rocksdb/c.h` and `db/c.cc`. This grows the public C API by **668 functions** while keeping `c.h` a single includable header with no `-I` requirement (so `bindgen` and other FFI tools keep working unchanged).

This branch reconciles the PR with ~4 months of `main` and addresses the outstanding review feedback (clang-tidy bot, the automated code review, the `c.h` self-containedness discussion, and pdillinger's points about `include/rocksdb` hygiene and `generated` marking).

## What changed on top of the original PR

### Reconciled with current `main`
- Merged current `main` (conflicts were confined to the generated/test files) and regenerated. Reconciled the 14 C API functions `main` added since the merge-base (e.g. `rocksdb_set_db_options`, the backup-engine rate limiters, `memtable_batch_lookup_optimization`, `optimize_multiget_for_io`, …) and restored 5 enum constants that upstream had added by hand (`rocksdb_txndb_write_policy_*`, `..._index_block_search_type_auto`, `rocksdb_blob_cache_read_byte`).

### Maintainer feedback (pdillinger)
- **No non-user-includable files in `include/rocksdb`.** Moved the hand-written templates out of `include/rocksdb/` and `db/` to `tools/c_api_gen/c_base.{h,cc}`. They were `#include`-ing generated fragments, which broke `make check-headers` and was shipped by `make install`. `include/rocksdb/` now contains only the user-facing, self-contained, `generated` `c.h`.
- `c.h` / `c.cc` carry the `// generated` marker.

### Backward compatibility (zero ABI break)
- The generator derived each wrapper's C type purely from the C++ field, which had silently changed **5 already-shipped signatures** (e.g. `rocksdb_writeoptions_disable_WAL` `int` → `unsigned char`). Added an ABI type-pinning layer (`tools/c_api_gen/abi_type_overrides.json`) so already-shipped functions keep their historical C signature (the body still casts to the real field type). A repo-wide diff against the merge-base now reports **0 ABI drift**.
- New `check_api_compatibility.py` gate (wired into CI + `make`) fails on any removed/changed public function **or** removed enum/typedef symbol, vs a reference revision. Intentional changes go in an allowlist with a reason.

### Correctness (from the automated review)
- Restored 5 option setters that were declared in `c.h` but **defined nowhere** (link failure for downstream bindings such as `rust-rocksdb`). Added `check_api_completeness.py` (dependency-free; runs in CI + `make`) asserting every declared function has exactly one definition — this is the gate that would have caught it.
- `CopyStringVector` now null-checks `malloc`; the WAL filter `std::move`s the `WriteBatch`; the backup exclude-files callback captures by value instead of the wrapper pointer.

### Build / CI robustness
- Removed the dead `C_API_CODEGEN_STAMP` Makefile prerequisite (it was a silent no-op).
- The `make check` staleness check is now opt-out-able (behind `SKIP_FORMAT_BUCK_CHECKS`) and skips gracefully when `clang++` is unavailable, so `make check` works without the codegen toolchain. CI remains the authoritative gate.
- Pinned `clang-format` consistently through `regen_all.py` / `verify_generated_up_to_date.py` (CI uses clang-format-21) so regeneration is byte-reproducible across environments.
- Cleared all 20 `clang-tidy` warnings the bot reported on `db/c.cc` changed lines (fixed in the `c_base.cc` template, not the generated output).
- Updated the internal Buck `c_test_bin` wrapper to expose generated `c_api_gen/*.inc` fragments as headers, so sandboxed Buck builds can compile `db/c_test.c` after the generated round-trip tests are included.

### Test coverage
- Added `gen_roundtrip_tests.py`, which derives **462 set→get→assert round-trip checks across 25 option objects** from the same generated fragments and wires them into `db/c_test.c`. Coverage now tracks the generated surface automatically.

### Docs
- Added the `unreleased_history/public_api_changes` note and fixed `claude_md/add_public_api.md`, which still told contributors to hand-edit the now-`generated` `c.h`/`c.cc`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14868

Test Plan:
- `make c_test && ./c_test` — **passes**, including the 462 generated round-trip assertions (a successful link also confirms the API is complete).
- `python3 tools/c_api_gen/check_api_completeness.py` — all 1737 declared functions defined exactly once.
- `python3 tools/c_api_gen/check_api_compatibility.py --ref <release>` — 1070 reference functions + 229 enum/typedef symbols preserved, 0 removed/changed.
- `python3 tools/c_api_gen/verify_generated_up_to_date.py` — generated output is stable.
- `include/rocksdb/c.h` confirmed self-contained (only `<stdbool.h>`, `<stddef.h>`, `<stdint.h>`).

cc xingbowang

- `buck2 build --flagfile fbcode//mode/dev fbcode//internal_repo_rocksdb/repo:c_test_bin` — passes.
- `buck2 build --flagfile fbcode//mode/dev --config fbcode.arch=aarch64 fbcode//internal_repo_rocksdb/repo:c_test_bin` — passes.

Reviewed By: pdillinger

Differential Revision: D109149150

Pulled By: xingbowang

fbshipit-source-id: 3417375345f360a4c78bdfe27e9850b89d0a226a
2026-06-24 10:45:42 -07:00
Peter Dillinger 30295a4b92 Fix db_stress rollback retry exhaustion under concurrent Resume Busy (#14877)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14877

The rollback-after-failed-commit retry loop in `db_stress_test_base.cc` (added in b0b52e1d14f3) counted every loop iteration — including iterations where `Resume()` returned Busy — against a single 100-count limit. With 32 concurrent threads, when write fault injection causes all prepared transaction commits and rollbacks to fail simultaneously, `Resume()` returns Busy (recovery in progress from another thread) for the entire 100-iteration budget (100 × 10ms = 1 second). Recovery itself keeps failing due to ongoing fault injection causing repeated background flush errors, so no thread ever gets a chance to perform an actual rollback.

The fix separates two budgets:
- `kMaxRollbackRetries` (100): counts only actual rollback attempts after `Resume()` returns OK
- `kMaxResumeBusyWaits` (3000): allows up to 30 seconds of busy-waiting for recovery to complete on another thread

Key triggering options: `write_fault_one_in=1000` + `txn_write_policy=1` (write-prepared) + `two_write_queues=1` + 32 threads + `error_recovery_with_no_fault_injection=1`.

Reviewed By: joshkang97

Differential Revision: D108561314

fbshipit-source-id: 03f4578ea33289685ad982208451f0584bf0535f
2026-06-23 15:12:23 -07:00
Xingbo Wang 1f20db7a31 Add FIFO KV-ratio compaction blog post (#14871)
Summary:
- Add a blog post explaining FIFO KV-ratio compaction for BlobDB-backed TTL workloads.
- Describe why the existing FIFO intra-L0 picker can repeatedly compact small BlobDB SST files and how the new KV-ratio target plus tiered merging strategy works.
- Add diagrams for the old picker behavior and the KV-ratio tiering flow, along with the required blog author metadata.

## Testing

- `git diff --check upstream/main...HEAD`

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14871

Reviewed By: joshkang97

Differential Revision: D109243987

Pulled By: xingbowang

fbshipit-source-id: 7ad07953f38c55c77ffb6510824ba8500db78c0e
2026-06-23 05:54:51 -07:00
Josh Kang f8987bc7b9 Include PR and version for range tombstone blog post (#14875)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14875

title

Reviewed By: pdillinger

Differential Revision: D109353179

fbshipit-source-id: c14c9ecbd29a8118b1d0f0689e2f29c162bd15e8
2026-06-22 16:07:39 -07:00
Maciej Szeszko 30f42e00ba fix package fault injection log parser in db stress fbpkg (#14874)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14874

### Problem

RocksDB [alert](https://www.internalfb.com/monitoring/alert/?alert_id=twshared38314.04.ldc2%40%23%24host_health.crashlooping_key.tw_agent.tw_agent.task_failure.app.count.60%40%23%24uhc%3A%20tupperware%20task%20crashlooping) fired for `tsp_ldc/RocksDB/rocks_on_ws_stress_test` after the job started crashlooping with `ModuleNotFoundError: No module named fault_injection_log_parser`.

### RCA
D101973626 added `import fault_injection_log_parser` to `db_crashtest.py`, but `rocksdb_db_stress_internal_repo` only packaged `crashtest.py` and `rocks_db_stress`, so the `release_test` fbpkg rolled to TW without the new helper module.

### Fix
Export `tools/fault_injection_log_parser.py` and include it in the `rocksdb_db_stress_internal_repo` fbpkg `path_actions` so it is installed next to `crashtest.py`; also add the BUCKLINT-required `network_access = network_access_utils.none()` on the existing `db_stress_auto_tuner_test` target.

Reviewed By: xingbowang

Differential Revision: D109346224

fbshipit-source-id: 3f770a7ad306d246f279641bf0f19142d05560c4
2026-06-22 14:05:47 -07:00
Josh Kang 6ccfbd4a86 Blog post for range tombstone conversion (#14862)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14862

Add blog post for range tombstone conversion.

Reviewed By: xingbowang

Differential Revision: D108946953

fbshipit-source-id: f2ded24a5452d2f56a12928b979a8f44930dde03
2026-06-22 12:04:16 -07:00
Xingbo Wang 5b324a2160 Fix CI: detect Windows MSVC toolchain (#14869)
Summary:
Update Windows CI to stop hardcoding Visual Studio 2022. The Windows build action now detects the installed Visual Studio C++ toolchain with vswhere, derives the matching CMake generator, and passes the corresponding version range to microsoft/setup-msbuild@v2.

Also update the Windows build steps to use the preinstalled JDK instead of installing Liberica JDK8 through Chocolatey, and build Snappy/RocksDB through cmake --build, instead of assuming fixed .sln names. Rename Windows CI jobs from VS2022-specific names to MSVC-neutral names, and move the nightly AVX2 Windows job to windows-8-core so PR and nightly CI use the same runner/toolchain path.

## Testing

- CI

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14869

Reviewed By: jaykorean

Differential Revision: D109176291

Pulled By: xingbowang

fbshipit-source-id: 848c5964922cf6e09b727b1ed96cbf475b325142
2026-06-22 09:22:13 -07:00
Maciej Szeszko 02941016d0 Fix seqno sync race with WriteUnprepared (#14864)
Summary:
# Summary

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14864

With `two_write_queues`=`true`, `WRITE_UNPREPARED` can allocate sequence numbers through `FetchAddLastAllocatedSequence()` before those numbers are published through `SetLastSequence()`. Error recovery can then create new memtable/WAL boundaries from a stale `LastSequence()`, which can later surface as "sequence number going backwards" corruption. Fix this by syncing `LastSequence()` to `LastAllocatedSequence()` at the recovery entry point after draining both write queues, and again at the recovery flush fence after `FlushAllColumnFamilies()` has released/reacquired the DB mutex and before `SwitchMemtable()` consumes `LastSequence()`. Apply the same fence to atomic recovery flushes.

# Perf comparative results

TLDR; no visible regression.

I ran a deterministic local recovery benchmark to check whether the new two-write-queue recovery fence adds measurable recovery latency.

## Workload (P2385173320)
  - Optimized build.
  - `TransactionDB` with `WRITE_UNPREPARED`.
  - `two_write_queues=true`.
  - Each iteration dirties the memtable with WUP transactions, forces a retryable flush IO error through `FaultInjectionTestFS`, re-enables the filesystem, then measures `DB::Resume()`.
  - 50 warmup recoveries + 500 measured recoveries per run.

## Why this is relevant
This targets the changed recovery path directly rather than measuring generic write throughput. The change only runs during recovery, so the useful signal is recovery latency around `ResumeImpl()` and the recovery flush fence.

## Code path exercised
  - `DB::Resume()`
  - `ErrorHandler::RecoverFromBGError(true)`
  - `DBImpl::ResumeImpl()`
  - new `two_write_queues_` recovery queue fence
  - non-atomic recovery `FlushMemTable()` fence before `SwitchMemtable()`

This does not cover atomic flush recovery or a writer-backlog stress case.

Comparative results:

| Revision | Attempt | resume_p50_us | resume_p95_us | Retryable BG errors |
| Parent |  | 1555.75 | 2092.22 | 550 |
| Fix | 1 | 1537.68 | 1905.65 | 550 |
| Fix | 2 | 1475.10 | 1883.26 | 550 |
| Fix | 3 | 1617.52 | 1993.97 | 550 |

Reviewed By: xingbowang, anand1976

Differential Revision: D108946310

fbshipit-source-id: 75fcb4bf0c9a932b5fbed7e934c7f14d981a1bf2
2026-06-20 22:57:11 -07:00
Maciej Szeszko 2d13302949 Fix Windows CI: run build-windows-vs2022 on windows-2022 runner (#14872)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14872

### Motivation

The `build-windows-vs2022` job in `pr-jobs.yml` was running on the `windows-8-core` runner, whose image no longer provides a Visual Studio 2022 installation. The job sets `CMAKE_GENERATOR: Visual Studio 17 2022`, so CMake's generator probes for an installed VS 2022 instance and aborts the Snappy dependency configure with:

```
CMake Error: Generator Visual Studio 17 2022 could not find any instance of Visual Studio.
```
The failure surfaces during the thirdparty Snappy build, before any RocksDB code is compiled, so it blocks the entire Windows PR signal.

### Changes

Switch the `build-windows-vs2022` job from `runs-on: windows-8-core` to `runs-on: windows-2022`. The GitHub-hosted `windows-2022` image ships VS 2022 with the Desktop C++ workload preinstalled, so the `Visual Studio 17 2022` generator resolves out of the box and the `CMAKE_GENERATOR` value stays consistent with the runner's installed toolchain. This also brings `pr-jobs.yml` in line with `nightly.yml`, whose `build-windows-vs2022-avx2` job already runs on `windows-2022`.

NOTE: `windows-2022` is the standard 2-core hosted runner (vs. the larger `windows-8-core` pool), so Windows builds will be somewhat slower; ccache keeps the compile cost mostly in cache hits.

Reviewed By: pdillinger

Differential Revision: D109229258

fbshipit-source-id: b9f6d9ef8b4080b1b1129ed0ae6a9b1da2b7b14c
2026-06-20 22:07:08 -07:00
Xingbo Wang 214869aacd Persist fault injection logs and fail fast on expected-state trace writes (#14651)
Summary:
- switch fault injection error recording from an in-memory ring buffer to per-run fixed-record binary logs under `TEST_TMPDIR/fault_injection_logs` (or `/tmp/fault_injection_logs`) so crash paths survive DB reopen cleanup
- keep the raw and decoded fault logs for external artifact collection/cleanup, and make `db_crashtest` print consistent blackbox/whitebox summaries after decoding
- make expected-state tracing fail fast on trace write failures and document offline trace inspection via `trace_analyzer`
- add coverage for binary log persistence/decoding/truncated-tail handling and keep info logs excluded from fault injection

Reviewed By: hx235

Differential Revision: D101973626

fbshipit-source-id: fdcb5b6370cf92a046e09b8d3391e80eecb66c23
2026-06-20 17:20:14 -07:00
anand1976 5bf78183db Update version to 11.6.0 for 11.5 release (#14861)
Summary:
- Bump version.h from 11.5.0 to 11.6.0
- Add `11.5.fb` to `check_format_compatible.sh`
- Cherry-pick HISTORY.md from `11.5.fb`
- Update folly hash in `folly.mk`
- Delete `unreleased_history/` note files

Part of 11.5 release workflow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14861

Reviewed By: anand1976

Differential Revision: D108971785

Pulled By: jaykorean

fbshipit-source-id: d4076d34f860c9caddfac38a5406e02112436cc2
2026-06-17 21:05:15 -07:00
226 changed files with 52607 additions and 4540 deletions

No files matched your search

@@ -1,9 +0,0 @@
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
@@ -0,0 +1,17 @@
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
+56 -7
View File
@@ -11,8 +11,49 @@ 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@v1.3.1
uses: microsoft/setup-msbuild@v2
with:
vs-version: ${{ steps.detect_vs.outputs.vs_version }}
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
@@ -32,15 +73,23 @@ 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 =====================
choco install liberica8jdk -y
if(!$?) { Exit $LASTEXITCODE }
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
mkdir $Env:THIRDPARTY_HOME
cd $Env:THIRDPARTY_HOME
echo "Building Snappy dependency..."
@@ -53,11 +102,11 @@ runs:
cd build
& cmake -G "$Env:CMAKE_GENERATOR" .. -DSNAPPY_BUILD_TESTS=OFF -DSNAPPY_BUILD_BENCHMARKS=OFF
if(!$?) { Exit $LASTEXITCODE }
msbuild Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
cmake --build . --config Debug --parallel 32 -- /p:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
echo ======================== Build RocksDB =========================
cd ${{ github.workspace }}
$env:Path = $env:JAVA_HOME + ";" + $env:Path
$env:Path = "$env:JAVA_HOME\bin;" + $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 ..
@@ -65,7 +114,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
msbuild build/rocksdb.sln /m:32 /p:LinkIncremental=false -property:Configuration=Debug -property:Platform=x64
cmake --build build --config Debug --parallel 32 -- /p:LinkIncremental=false /p:Platform=x64
if(!$?) { Exit $LASTEXITCODE }
echo ========================= Test RocksDB =========================
$suiteRun = "${{ inputs.suite-run }}"
+2 -10
View File
@@ -714,8 +714,6 @@ jobs:
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.default_model }}
CODEX_REASONING_EFFORT: ${{ steps.budget_codex.outputs.effort }}
BASE_SHA: ${{ steps.pr_info.outputs.base_sha }}
PR_TITLE: ${{ steps.pr_info.outputs.pr_title }}
run: |
if [ -z "${OPENAI_API_KEY}" ]; then
echo "OPENAI_API_KEY not configured, skipping Codex review"
@@ -723,9 +721,7 @@ jobs:
fi
mkdir -p "${CODEX_HOME}"
set +e
codex exec review \
--base "${BASE_SHA}" \
--title "${PR_TITLE}" \
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
@@ -1340,8 +1336,6 @@ jobs:
CODEX_HOME: /tmp/codex-home
CODEX_MODEL: ${{ inputs.selected_model != '' && inputs.selected_model || inputs.default_model }}
CODEX_REASONING_EFFORT: ${{ steps.manual_budget_codex.outputs.effort }}
BASE_SHA: ${{ steps.pr_info.outputs.base_sha }}
PR_TITLE: ${{ steps.pr_info.outputs.pr_title }}
COMMAND_TYPE: ${{ steps.request.outputs.type }}
run: |
mkdir -p "${CODEX_HOME}"
@@ -1354,9 +1348,7 @@ jobs:
--output-last-message codex-review-output.md \
- < /tmp/prompt.txt > codex-review-execution.log 2>&1
else
codex exec review \
--base "${BASE_SHA}" \
--title "${PR_TITLE}" \
codex exec \
-m "${CODEX_MODEL}" \
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
--dangerously-bypass-approvals-and-sandbox \
+32 -14
View File
@@ -40,6 +40,7 @@ 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
@@ -52,8 +53,18 @@ jobs:
with:
script: |
const fs = require('fs');
if (!fs.existsSync(process.env.COMMENT_FILE) ||
!fs.existsSync('pr_number.txt')) {
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)) {
core.info('No review results found; skipping.');
return;
}
@@ -61,16 +72,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(process.env.COMMENT_FILE, 'utf8');
const body = fs.readFileSync(commentFile, 'utf8');
const prNumber = parseInt(
fs.readFileSync('pr_number.txt', 'utf8').trim(),
fs.readFileSync(prNumberFile, 'utf8').trim(),
10
);
const trigger = fs.existsSync('trigger_type.txt') ?
fs.readFileSync('trigger_type.txt', 'utf8').trim() :
const trigger = fs.existsSync(triggerTypeFile) ?
fs.readFileSync(triggerTypeFile, 'utf8').trim() :
'auto';
const headSha = fs.existsSync('head_sha.txt') ?
fs.readFileSync('head_sha.txt', 'utf8').trim() :
const headSha = fs.existsSync(headShaFile) ?
fs.readFileSync(headShaFile, 'utf8').trim() :
'';
const shortSha = headSha ? headSha.substring(0, 7) : '';
const runId = process.env.RUN_ID;
@@ -119,12 +130,16 @@ jobs:
with:
script: |
const fs = require('fs');
if (!fs.existsSync('trigger_type.txt')) return;
const trigger = fs.readFileSync('trigger_type.txt', 'utf8').trim();
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 (trigger !== 'manual') return;
if (!fs.existsSync('comment_id.txt')) return;
if (!fs.existsSync(commentIdFile)) return;
const commentId = parseInt(
fs.readFileSync('comment_id.txt', 'utf8').trim(),
fs.readFileSync(commentIdFile, 'utf8').trim(),
10
);
if (!commentId) return;
@@ -147,6 +162,7 @@ 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
@@ -155,9 +171,11 @@ jobs:
with:
script: |
const fs = require('fs');
if (!fs.existsSync('comment_id.txt')) return;
const path = require('path');
const commentIdFile = path.join('_artifact', 'comment_id.txt');
if (!fs.existsSync(commentIdFile)) return;
const commentId = parseInt(
fs.readFileSync('comment_id.txt', 'utf8').trim(),
fs.readFileSync(commentIdFile, 'utf8').trim(),
10
);
if (!commentId) return;
+11 -3
View File
@@ -25,6 +25,7 @@ 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
@@ -33,12 +34,19 @@ jobs:
with:
script: |
const fs = require('fs');
if (!fs.existsSync('clang-tidy-comment.md') || !fs.existsSync('pr_number.txt')) {
if (!fs.existsSync('_artifact/clang-tidy-comment.md') ||
!fs.existsSync('_artifact/pr_number.txt')) {
core.info('No clang-tidy results found; skipping.');
return;
}
const body = fs.readFileSync('clang-tidy-comment.md', 'utf8');
const prNumber = parseInt(fs.readFileSync('pr_number.txt', 'utf8').trim());
const body = fs.readFileSync(
'_artifact/clang-tidy-comment.md',
'utf8'
);
const prNumber = parseInt(
fs.readFileSync('_artifact/pr_number.txt', 'utf8').trim(),
10
);
const post = require('./.github/scripts/post-pr-comment.js');
await post({
@@ -22,4 +22,3 @@ jobs:
provider: claude
result_artifact_name: claude-review-result
comment_file: claude-review-comment.md
secrets: inherit
@@ -22,4 +22,3 @@ jobs:
provider: codex
result_artifact_name: codex-review-result
comment_file: codex-review-comment.md
secrets: inherit
+3 -8
View File
@@ -7,14 +7,9 @@
name: Codex Code Review
on:
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]
# Automatic PR reviews are temporarily disabled while Codex authentication is
# repaired. Manual review requests remain available through issue comments
# and workflow_dispatch.
issue_comment:
types: [created]
+2 -3
View File
@@ -113,11 +113,10 @@ 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-vs2022-avx2:
build-windows-msvc-avx2:
if: ${{ github.repository_owner == 'facebook' }}
runs-on: windows-2022
runs-on: windows-8-core
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_PORTABLE: AVX2
steps:
- uses: actions/checkout@v4.1.0
+22 -11
View File
@@ -314,6 +314,23 @@ 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`.
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 '*'
git fetch --no-tags --depth=1 origin main
CXX=clang++-21 make check-c-api-gen \
CHECK_C_API_GEN_STRICT=1 \
API_COMPAT_REF=FETCH_HEAD \
CLANG_FORMAT_BINARY=clang-format-21
- uses: "./.github/actions/teardown-ccache"
if: always()
- uses: "./.github/actions/post-steps"
@@ -525,8 +542,8 @@ jobs:
- uses: "./.github/actions/post-steps"
# ======================== Windows with Tests ======================= #
# NOTE: some windows jobs are in "nightly" to save resources
build-windows-vs2022:
if: needs.config.outputs.only_job == 'build-windows-vs2022' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
build-windows-msvc:
if: needs.config.outputs.only_job == 'build-windows-msvc' || (needs.config.outputs.only_job == '' && github.repository_owner == 'facebook')
needs: config
runs-on: windows-8-core
strategy:
@@ -543,7 +560,6 @@ jobs:
suite_run: ""
run_java: "true"
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_PORTABLE: 1
steps:
- uses: actions/checkout@v4.1.0
@@ -603,7 +619,6 @@ 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
@@ -615,7 +630,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/install-jdk8-on-macos"
- uses: "./.github/actions/setup-jdk-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Set Java Environment
run: |-
@@ -631,8 +646,6 @@ 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
@@ -643,7 +656,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/install-jdk8-on-macos"
- uses: "./.github/actions/setup-jdk-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Set Java Environment
run: |-
@@ -659,8 +672,6 @@ 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
@@ -671,7 +682,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/install-jdk8-on-macos"
- uses: "./.github/actions/setup-jdk-on-macos"
- uses: "./.github/actions/pre-steps-macos"
- name: Set Java Environment
run: |-
+4
View File
@@ -1,5 +1,7 @@
make_config.mk
rocksdb.pc
.build_signature
build_tools/__pycache__/
*.a
*.arc
@@ -88,6 +90,8 @@ fbcode/
fbcode
buckifier/*.pyc
buckifier/__pycache__
tools/__pycache__/
tools/c_api_gen/__pycache__/
.arcconfig
compile_commands.json
+3
View File
@@ -35,6 +35,7 @@ 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",
@@ -5795,3 +5796,5 @@ cpp_unittest_wrapper(name="write_unprepared_transaction_test",
export_file(name = "tools/db_crashtest.py")
export_file(name = "tools/fault_injection_log_parser.py")
+64 -36
View File
@@ -38,7 +38,7 @@ This document provides guidance for generating and reviewing code in the RocksDB
**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. 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, 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.
**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.
@@ -243,27 +243,21 @@ from an implementation detail instead of an explicit option.
* Don't manually edit BUCK file, after updating src.mk, run
/usr/local/bin/python3 buckifier/buckify_rocksdb.py to update it
* 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.
### When to run `make clean` (avoid mixing build modes)
### Avoiding mixed build modes with Make (use `AUTO_CLEAN=1`)
The Makefile does **not** track build mode, so object files from a prior
build are silently reused even when compiled with different flags, leading
to confusing linker errors, sanitizer false negatives, ODR violations, or
"phantom" bugs.
Run `make clean` before switching any of these:
* **`ASSERT_STATUS_CHECKED=1` ↔ unset** — changes the `Status` class layout (ABI break).
* **Sanitizer builds** — toggling any of `COMPILE_WITH_ASAN=1`,
`COMPILE_WITH_UBSAN=1`, `COMPILE_WITH_TSAN=1` on/off.
* `DEBUG_LEVEL=0` (release) ↔ `DEBUG_LEVEL=1` (debug, default for `make dbg`).
* Different compilers, `OPT` levels, or other flags affecting codegen/ABI.
**Notable exception:** `DEBUG_LEVEL=2` can be safely mixed with
`DEBUG_LEVEL=1` — rebuild a subset of files with `DEBUG_LEVEL=2` to get
extra/more accurate runtime checks for those files without a full clean.
When in doubt, `make clean` is cheap insurance compared to chasing a
phantom bug.
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.
### Source checks
* Run `make check-sources` before committing. This catches non-ASCII
@@ -272,6 +266,22 @@ phantom bug.
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.
@@ -288,6 +298,7 @@ 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 |
@@ -302,6 +313,12 @@ 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
reusable utility functions to reduce code duplication due to copy past between
@@ -309,13 +326,22 @@ rather than relying on libstdc++ transitive includes.
* 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.
* 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:
* 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):
```bash
COERCE_CONTEXT_SWITCH=1 make {test_binary}
./{test_binary} --gtest_filter="*YourTestName*" --gtest_repeat=5
COERCE_CONTEXT_SWITCH=1 build_tools/rockstest.sh {test_binary} -r100 \
--gtest_filter="*YourTestName*"
```
* For CI-style flaky tests that do not reproduce with `gtest_parallel.py`,
`--gtest_repeat`, or normal coerce-mode runs, inspect
@@ -372,12 +398,13 @@ rather than relying on libstdc++ transitive includes.
* Blog post authors must be defined in `docs/_data/authors.yml` to be displayed
### Final verification of the change
* 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.
* 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.
### Monitoring make check progress
* Use `make check-progress` to get machine-parseable JSON progress while
@@ -385,7 +412,7 @@ rather than relying on libstdc++ transitive includes.
builds without timeout issues.
* Run `make check` in background, then poll progress:
```bash
make check &
AUTO_CLEAN=1 make check &
# Poll periodically:
make check-progress
```
@@ -410,9 +437,10 @@ rather than relying on libstdc++ transitive includes.
### Executing benchmark using db_bench
* Since the goal is to measure performance, we need to build a release binary
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`.
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.
### Formatting code
* After making change, use `make format-auto` to auto-apply formatting without
+12 -9
View File
@@ -420,13 +420,6 @@ 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)
@@ -705,10 +698,19 @@ if(USE_FOLLY)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
# Fix gflags library name for debug builds
# 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.
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_INST_PATH}/lib/libgflags_debug.so.2.2")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_DEBUG_SHARED_LIB}")
endif()
endif()
@@ -775,6 +777,7 @@ 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
+19 -4
View File
@@ -1,12 +1,27 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.5.2 (06/25/2026)
## 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
## 11.5.1 (06/24/2026)
### Bug Fixes
* 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)
+189 -3
View File
@@ -288,7 +288,7 @@ endif
endif
export JAVAC_ARGS
CLEAN_FILES += make_config.mk rocksdb.pc
CLEAN_FILES += rocksdb.pc
ifeq ($(V), 1)
$(info $(shell uname -a))
@@ -764,7 +764,6 @@ 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
#-----------------------------------------------
@@ -809,7 +808,8 @@ 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
uninstall analyze tools tools_lib check-headers checkout_folly clang-tidy \
check-c-api-c_test
# 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.
@@ -1127,8 +1127,89 @@ ifndef SKIP_FORMAT_BUCK_CHECKS
$(MAKE) check-buck-targets
$(MAKE) check-sources
$(MAKE) check-workflow-yaml
$(MAKE) check-c-api-gen
endif
# 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; locally it falls back to main /
# 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); detect one the way the generator
# does (a clang in $(CXX) -- which may be ccache-prefixed/versioned -- else a
# bare/versioned clang++ on PATH) rather than testing $(CXX) verbatim.
@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; \
have_clang=""; \
for c in $$(printf '%s\n' $(CXX) | grep -i clang) clang++ clang++-21 clang++-20 clang++-19 clang++-18 clang++-17 clang++-16 clang++-15 clang++-14 clang++-13; do \
if command -v "$$c" >/dev/null 2>&1; then have_clang=1; break; fi; \
done; \
if [ -n "$$have_clang" ]; then \
$(PYTHON) tools/c_api_gen/verify_generated_up_to_date.py $$cf_arg; \
elif [ -n "$$strict" ]; then \
echo "ERROR: no clang++ found 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; install clang++ or set CXX to a clang to enable)"; \
fi
# 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
$(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
# TODO add ldb_tests
check_some: $(ROCKSDBTESTS_SUBSET)
for t in $(ROCKSDBTESTS_SUBSET); do echo "===== Running $$t (`date`)"; ./$$t || exit 1; done
@@ -1278,8 +1359,13 @@ 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)
@@ -1294,6 +1380,11 @@ clean-rocks:
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
clean-rocksjava: clean-rocks
rm -rf jl jls
@@ -2327,6 +2418,101 @@ 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 check-c-api-gen \
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 --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`; \
+1
View File
@@ -360,6 +360,7 @@ 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,6 +118,12 @@ 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"""
-14
View File
@@ -27,7 +27,6 @@
# -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:
@@ -424,19 +423,6 @@ 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 \
+4 -1
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
# 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).
#
# Check for some simple mistakes in public headers (on the command line)
# that should prevent commit or push
+14 -15
View File
@@ -1,21 +1,20 @@
# 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/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
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
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/09703139cfc376bd8a82642385a0e97726b28287/1.0.6/platform010/76ebdda
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
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
NUMA_BASE=/mnt/gvfs/third-party2/numa/5b602edd46fda54cdd7ea45f77dbe4061206e174/2.0.11/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
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/f3cce2e1a79ac8d64e102886603a6997d6e88640/1.8/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/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
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
+4 -13
View File
@@ -87,15 +87,6 @@ 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
@@ -104,7 +95,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 $TBB_INCLUDE"
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
STDLIBS="-L $GCC_BASE/lib64"
@@ -150,17 +141,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 $TBB_LIBS"
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB"
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"
# required by libtbb
# dynamic loading
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 $TBB_LIBS"
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
VALGRIND_VER="$VALGRIND_BASE/bin/"
+4 -9
View File
@@ -82,11 +82,6 @@ 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"
@@ -101,7 +96,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 $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
STDLIBS="-L $GCC_BASE/lib64"
@@ -157,18 +152,18 @@ 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 $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $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"
# required by libtbb
# dynamic loading
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 $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
VALGRIND_VER="$VALGRIND_BASE/bin/"
+18
View File
@@ -0,0 +1,18 @@
#!/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
@@ -0,0 +1,959 @@
# 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
@@ -0,0 +1,67 @@
#!/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
@@ -0,0 +1,76 @@
#!/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" "$@"
+24 -9
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,17 +75,33 @@ touch "$OUTPUT"
echo "Writing dependencies to $OUTPUT"
# Compilers locations
GCC_BASE=`readlink -f $TP2_LATEST/gcc/11.x/centos8-native/*/`
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/15/platform010/*/`
# 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/*/`
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
@@ -94,12 +110,11 @@ 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 centos8-native
get_lib_base binutils LATEST centos9-native
get_lib_base valgrind LATEST platform010
git diff $OUTPUT
@@ -0,0 +1,39 @@
// @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);
}
@@ -0,0 +1,32 @@
// @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);
@@ -0,0 +1,21 @@
// @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;
}
@@ -0,0 +1,19 @@
// @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);
@@ -0,0 +1,189 @@
// @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));
}
@@ -0,0 +1,126 @@
// @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);
+255
View File
@@ -0,0 +1,255 @@
// @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();
}
+173
View File
@@ -0,0 +1,173 @@
// @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);
@@ -0,0 +1,84 @@
// @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);
}
@@ -0,0 +1,57 @@
// @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);
@@ -0,0 +1,501 @@
// @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;
}
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 */
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 */
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;
}
@@ -0,0 +1,361 @@
// @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 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 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 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
@@ -0,0 +1,84 @@
// @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;
}
@@ -0,0 +1,67 @@
// @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);
@@ -0,0 +1,236 @@
// @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);
}
@@ -0,0 +1,161 @@
// @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
+371
View File
@@ -0,0 +1,371 @@
// @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));
}
+245
View File
@@ -0,0 +1,245 @@
// @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);
@@ -0,0 +1,77 @@
// @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());
}
@@ -0,0 +1,54 @@
// @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);
@@ -0,0 +1,87 @@
// @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));
}
@@ -0,0 +1,58 @@
// @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);
@@ -0,0 +1,58 @@
// @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());
}
@@ -0,0 +1,47 @@
// @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,6 +127,24 @@ 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.IsEnd()];
HandleImpl* h = &arr[next_with_shift.GetNext()];
// 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
+24 -1
View File
@@ -421,6 +421,30 @@ 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
@@ -509,4 +533,3 @@ Common option types used in serialization:
- [ ] unreleased_history markdown file
- [ ] Java API (for BlockBasedTableOptions)
- [ ] C API (if needed)
+46 -5
View File
@@ -168,9 +168,18 @@ Status YourNewAPI(const YourAPIOptions& /*options*/,
### Step 5: Add C API Bindings
**Header:** `include/rocksdb/c.h`
> **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.
\`\`\`c
**Header (declaration → `tools/c_api_gen/c_base.h`):**
```c
// Basic version
extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api(
rocksdb_t* db,
@@ -191,7 +200,7 @@ extern ROCKSDB_LIBRARY_API void rocksdb_your_new_api_opt(
char** errptr);
\`\`\`
**Implementation:** `db/c.cc`
**Implementation (→ `tools/c_api_gen/c_base.cc`):**
\`\`\`cpp
void rocksdb_your_new_api(rocksdb_t* db, const char* start_key,
@@ -217,6 +226,38 @@ 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
@@ -482,8 +523,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 | `include/rocksdb/c.h` | ✓ |
| C API Implementation | `db/c.cc` | ✓ |
| 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`) | ✓ |
| 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` | ✓ |
-4
View File
@@ -44,10 +44,6 @@ 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
@@ -1,33 +0,0 @@
# - 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()
+18
View File
@@ -11,6 +11,24 @@
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);
} // namespace ROCKSDB_NAMESPACE
+6 -1
View File
@@ -32,6 +32,9 @@ 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 =
@@ -125,7 +128,9 @@ Status BlobGarbageMeter::ProcessEntityBlobReferences(
!s.ok()) {
return s;
}
AddFlow(file_number, blob_bytes, is_inflow);
if (file_number != kInvalidBlobFileNumber) {
AddFlow(file_number, blob_bytes, is_inflow);
}
return Status::OK();
});
}
+18
View File
@@ -151,6 +151,24 @@ 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);
+111
View File
@@ -0,0 +1,111 @@
// 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 <array>
#include <cstring>
#include <string>
#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"
namespace ROCKSDB_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("Could not read complete blob record");
}
// 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 does not match blob index");
}
if (read_options.verify_checksums) {
uint32_t stored = DecodeFixed32(record + payload_size + 1);
stored -= ChecksumModifierForContext(base_context_checksum, record_offset);
const uint32_t computed = ComputeBuiltinChecksumWithLastByte(
checksum_type, record, payload_size, record[payload_size]);
if (stored != computed) {
return Status::Corruption("Blob record checksum mismatch in " +
file->file_name() + " offset " +
std::to_string(record_offset) + " size " +
std::to_string(payload_size));
}
}
if (compression != kNoCompression) {
return Status::Corruption("Blob record compression is not supported");
}
return Status::OK();
}
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
+92
View File
@@ -0,0 +1,92 @@
// 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);
// 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
+16 -2
View File
@@ -7,6 +7,7 @@
#include <sstream>
#include <string>
#include "db/blob/blob_constants.h"
#include "rocksdb/compression_type.h"
#include "util/coding.h"
#include "util/compression.h"
@@ -57,6 +58,14 @@ 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;
}
@@ -125,8 +134,13 @@ class BlobIndex {
if (IsInlined()) {
oss << "[inlined blob] value:" << value_.ToString(output_hex);
} else {
oss << "[blob ref] file:" << file_number_ << " offset:" << offset_
<< " size:" << size_
oss << "[blob ref] file:";
if (IsSameFile()) {
oss << "same";
} else {
oss << file_number_;
}
oss << " offset:" << offset_ << " size:" << size_
<< " compression: " << CompressionTypeToString(compression_);
}
+93
View File
@@ -12,7 +12,10 @@
#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"
@@ -305,6 +308,96 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
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) {
return Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
}
// 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;
}
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;
}
void BlobSource::MultiGetBlob(const ReadOptions& read_options,
autovector<BlobFileReadRequests>& blob_reqs,
uint64_t* bytes_read) {
+37
View File
@@ -25,6 +25,8 @@ struct MutableCFOptions;
class Status;
class FilePrefetchBuffer;
class Slice;
class RandomAccessFileReader;
enum ChecksumType : char;
// BlobSource is a class that provides universal access to blobs, regardless of
// whether they are in the blob cache, secondary cache, or (remote) storage.
@@ -59,6 +61,41 @@ class BlobSource {
FilePrefetchBuffer* prefetch_buffer, 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);
// Read multiple blobs from the underlying cache or blob file(s).
//
// If successful, returns ok and sets "result" in the elements of "blob_reqs"
+445
View File
@@ -25,6 +25,9 @@
#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"
@@ -41,6 +44,18 @@ 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
@@ -330,6 +345,436 @@ 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());
}
class PlainBlobValueFilterV3 : public CompactionFilter {
public:
PlainBlobValueFilterV3(std::atomic<int>* filter_call_count,
+6505 -1872
View File
File diff suppressed because it is too large. Load diff
+1757 -4
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -678,6 +678,9 @@ ColumnFamilyData::ColumnFamilyData(
internal_stats_->GetBlobFileReadHist(), io_tracer));
blob_source_.reset(new BlobSource(ioptions_, mutable_cf_options_, db_id,
db_session_id, blob_file_cache_.get()));
// Let the table cache route same-file ("embedded") blob reads through the
// blob value cache + stats. Both objects share this CFD's lifetime.
table_cache_->SetBlobSource(blob_source_.get());
if (ioptions_.compaction_style == kCompactionStyleLevel) {
compaction_picker_.reset(
+47 -5
View File
@@ -777,6 +777,40 @@ static std::unordered_map<std::string, OptionTypeInfo>
OptionTypeFlags::kNone}},
};
namespace {
// Like OptionTypeInfo::Array<T, kSize> but deserialization tolerates
// size mismatches: extra elements beyond kSize are silently ignored,
// missing elements are zero-filled. Serialization and comparison are
// inherited from Array<T, kSize> unchanged; only the parse
// (deserialize) callback is overridden.
template <typename T, size_t kSize>
OptionTypeInfo SizeTolerantDeserializeArray(int offset,
OptionVerificationType verification,
OptionTypeFlags flags,
const OptionTypeInfo& elem_info,
char separator = ':') {
return OptionTypeInfo::Array<T, kSize>(offset, verification, flags, elem_info,
separator)
.SetParseFunc([elem_info, separator](
const ConfigOptions& opts, const std::string& name,
const std::string& value, void* addr) {
std::vector<T> parsed;
Status s =
ParseVector<T>(opts, elem_info, separator, name, value, &parsed);
if (!s.ok()) {
return Status(std::move(s));
}
auto* arr = static_cast<std::array<T, kSize>*>(addr);
for (size_t i = 0; i < kSize; ++i) {
(*arr)[i] = (i < parsed.size()) ? parsed[i] : T{};
}
return Status::OK();
});
}
} // namespace
static std::unordered_map<std::string, OptionTypeInfo>
compaction_stats_type_info = {
{"micros",
@@ -868,12 +902,20 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct InternalStats::CompactionStats, count),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
// The remote worker may serialize a different number of
// CompactionReason counts than this primary expects (RocksDB versions
// can disagree on kNumOfReasons). Deserialization tolerates the
// mismatch: extra trailing elements are dropped, missing ones are
// zero-filled.
//
// The serialized width is intentionally kept at kNumOfReasons - 1 (the
// reduced width) so this change does NOT alter what is written -- only
// the read path becomes tolerant. Widening the serialized width to the
// full kNumOfReasons is a separate, later change that must not land
// until this tolerant read has been deployed to every primary,
// including rollback targets.
{"counts",
OptionTypeInfo::Array<
/* In release 11.2, a new compaction reason was added. This broken
* the reader and writer. To unblock release 11.1, we temporarily
* reduce the count array size to the old one. TODO add a proper
* serialization and deserialization method. */
SizeTolerantDeserializeArray<
int, static_cast<int>(CompactionReason::kNumOfReasons) - 1>(
offsetof(struct InternalStats::CompactionStats, counts),
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
+129
View File
@@ -3,6 +3,8 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include <memory>
#include "db/db_test_util.h"
#include "file/file_util.h"
#include "port/stack_trace.h"
@@ -12,6 +14,71 @@
namespace ROCKSDB_NAMESPACE {
namespace {
// Append ":extra_value" to every "counts=...;" field in a serialized
// CompactionServiceResult string to simulate a worker with more
// CompactionReason enums than the reader. Loops because the serialized
// result contains multiple CompactionStats structs (output_level_stats,
// proximal_level_stats), each with its own "counts=" field.
void AppendToCountsFields(std::string* s, const std::string& extra_value) {
const std::string prefix = "counts=";
size_t pos = s->find(prefix);
while (pos != std::string::npos) {
// Find the semicolon that terminates this field's value.
size_t semi = s->find(';', pos + prefix.size());
assert(semi != std::string::npos);
// Insert ":extra_value" just before the semicolon.
s->insert(semi, ":" + extra_value);
// Advance past the inserted text to find the next occurrence.
pos = s->find(prefix, semi + extra_value.size() + 2);
}
}
// Remove the last colon-separated element from every "counts=...;" field
// to simulate a worker with fewer CompactionReason enums than the reader.
// Loops for the same reason as AppendToCountsFields (multiple counts fields).
void RemoveLastFromCountsFields(std::string* s) {
const std::string prefix = "counts=";
size_t pos = s->find(prefix);
while (pos != std::string::npos) {
size_t val_begin = pos + prefix.size();
size_t semi = s->find(';', val_begin);
assert(semi != std::string::npos);
// Find the last ':' separator within the value.
size_t last_sep = s->rfind(':', semi - 1);
assert(last_sep != std::string::npos && last_sep >= val_begin);
// Erase from the last ':' up to (not including) the ';'.
// e.g. "counts=0:3:5;" -> erase ":5" -> "counts=0:3;"
s->erase(last_sep, semi - last_sep);
pos = s->find(prefix, last_sep);
}
}
// Count the colon-separated elements in the first "counts=...;" field of a
// serialized CompactionServiceResult. Used to assert the serialized width
// (how many CompactionReason counts this binary writes).
size_t CountFirstCountsFieldElements(const std::string& s) {
const std::string prefix = "counts=";
size_t pos = s.find(prefix);
assert(pos != std::string::npos);
size_t val_begin = pos + prefix.size();
size_t semi = s.find(';', val_begin);
assert(semi != std::string::npos);
if (semi == val_begin) {
return 0;
}
size_t count = 1;
for (size_t i = val_begin; i < semi; ++i) {
if (s[i] == ':') {
++count;
}
}
return count;
}
} // namespace
class MyTestCompactionService : public CompactionService {
public:
MyTestCompactionService(
@@ -1609,6 +1676,68 @@ TEST_F(CompactionServiceTest, InvalidResultFallsBackToLocal) {
my_cs->GetFinalCompactionServiceJobStatus());
}
TEST_F(CompactionServiceTest, CompatCheckCountsWidthTolerance) {
// Number of per-CompactionReason counts this binary serializes and expects.
constexpr size_t kExpected =
static_cast<size_t>(CompactionReason::kNumOfReasons) - 1;
constexpr size_t kLastIndex = kExpected - 1;
constexpr int kFirstCount = 3;
constexpr int kLastCount = 5;
CompactionServiceResult result;
result.status = Status::OK();
result.output_level = 2;
result.output_path = "/tmp/output";
result.internal_stats.output_level_stats.counts[0] = kFirstCount;
result.internal_stats.output_level_stats.counts[kLastIndex] = kLastCount;
std::string serialized;
ASSERT_OK(result.Write(&serialized));
// Guard the produced format: this binary writes exactly kExpected counts.
// That element count is what a remote worker hands to the primary to read,
// so pinning it catches an accidental change to what is written.
ASSERT_EQ(CountFirstCountsFieldElements(serialized), kExpected);
// Peer agrees on kNumOfReasons: every written count is read back unchanged.
{
CompactionServiceResult parsed;
ASSERT_OK(CompactionServiceResult::Read(serialized, &parsed));
ASSERT_OK(parsed.status);
const auto& counts = parsed.internal_stats.output_level_stats.counts;
ASSERT_EQ(counts[0], kFirstCount);
ASSERT_EQ(counts[kLastIndex], kLastCount);
}
// Peer serialized one more count than this binary knows (a worker with an
// extra CompactionReason): the surplus trailing count is ignored, and the
// counts this binary understands are read unchanged.
{
std::string from_wider_peer = serialized;
AppendToCountsFields(&from_wider_peer, "7");
ASSERT_EQ(CountFirstCountsFieldElements(from_wider_peer), kExpected + 1);
CompactionServiceResult parsed;
ASSERT_OK(CompactionServiceResult::Read(from_wider_peer, &parsed));
ASSERT_OK(parsed.status);
const auto& counts = parsed.internal_stats.output_level_stats.counts;
ASSERT_EQ(counts[0], kFirstCount);
ASSERT_EQ(counts[kLastIndex], kLastCount);
}
// Peer serialized one fewer count than this binary knows (a worker missing
// the newest CompactionReason): the absent trailing count reads as zero.
{
std::string from_narrower_peer = serialized;
RemoveLastFromCountsFields(&from_narrower_peer);
ASSERT_EQ(CountFirstCountsFieldElements(from_narrower_peer), kExpected - 1);
CompactionServiceResult parsed;
ASSERT_OK(CompactionServiceResult::Read(from_narrower_peer, &parsed));
ASSERT_OK(parsed.status);
const auto& counts = parsed.internal_stats.output_level_stats.counts;
ASSERT_EQ(counts[0], kFirstCount);
ASSERT_EQ(counts[kLastIndex], 0);
}
}
TEST_F(CompactionServiceTest, SubCompaction) {
Options options = CurrentOptions();
options.max_subcompactions = 10;
+57 -7
View File
@@ -207,8 +207,9 @@ TEST_F(DBBasicTest, OptimizeManifestForRecoveryEmitsPerCFWhenLogAdvances) {
ASSERT_EQ("v", Get("k"));
}
// With track_and_verify_wals_in_manifest=true, the wal_deletion edit
// must STILL be emitted (the DeleteWalsBefore record is required).
// With track_and_verify_wals_in_manifest=true and a clean close, the
// close-time code already advances WalSet markers. Recovery's
// DeleteWalsBefore is redundant and should be skipped.
TEST_F(DBBasicTest, OptimizeManifestForRecoveryPreservesWalTracking) {
Options options = CurrentOptions();
options.create_if_missing = true;
@@ -224,7 +225,7 @@ TEST_F(DBBasicTest, OptimizeManifestForRecoveryPreservesWalTracking) {
Reopen(options);
counters.Uninstall();
ASSERT_EQ(0, counters.wal_deletion.load());
ASSERT_EQ(1, counters.wal_deletion.load());
}
// best_efforts_recovery requires a fresh MANIFEST + CURRENT to be
@@ -497,11 +498,60 @@ TEST_F(DBBasicTest, OptimizeManifestForRecoveryEmitsWalDeletionWhenTracking) {
counters.Uninstall();
ASSERT_GT(counters.per_cf.load(), 0);
ASSERT_EQ(1, records.load());
ASSERT_EQ(0, records.load());
ASSERT_GT(dbfull()->GetVersionSet()->GetWalSet().GetMinWalNumberToKeep(), 0u);
ASSERT_EQ("v", Get("k"));
}
// Zero MANIFEST growth on clean reopen with optimize_manifest_for_recovery=1,
// reuse_manifest_on_open=1, and track_and_verify_wals_in_manifest=1.
// Regression test for the stress test failure where a redundant
// DeleteWalsBefore edit was written during recovery.
TEST_F(DBBasicTest, OptimizeManifestForRecoveryZeroGrowthOnCleanReopen) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.optimize_manifest_for_recovery = true;
options.reuse_manifest_on_open = true;
options.track_and_verify_wals_in_manifest = true;
options.avoid_flush_during_recovery = true;
options.write_dbid_to_manifest = false;
DestroyAndReopen(options);
ASSERT_OK(Put("k", "v"));
ASSERT_OK(Flush());
Close();
// Get MANIFEST size after close
std::vector<std::string> files;
ASSERT_OK(env_->GetChildren(dbname_, &files));
uint64_t manifest_size_before = 0;
for (const auto& f : files) {
if (f.find("MANIFEST-") == 0) {
ASSERT_OK(env_->GetFileSize(dbname_ + "/" + f, &manifest_size_before));
break;
}
}
ASSERT_GT(manifest_size_before, 0u);
// Reopen - should not grow MANIFEST at all
Reopen(options);
// Get MANIFEST size after reopen
files.clear();
ASSERT_OK(env_->GetChildren(dbname_, &files));
uint64_t manifest_size_after = 0;
for (const auto& f : files) {
if (f.find("MANIFEST-") == 0) {
ASSERT_OK(env_->GetFileSize(dbname_ + "/" + f, &manifest_size_after));
break;
}
}
ASSERT_EQ(manifest_size_before, manifest_size_after)
<< "MANIFEST grew by " << (manifest_size_after - manifest_size_before)
<< " bytes on clean reopen; expected zero growth";
ASSERT_EQ("v", Get("k"));
}
// Regression for the close-time marker path: if a new WAL is created while an
// otherwise-empty user CF stays empty, Close() must reserve the next file
// number before persisting SetLogNumber(cur_wal + 1) for that CF.
@@ -5459,9 +5509,9 @@ class DBBasicTestMultiGet : public DBTestBase {
std::vector<std::string> cf_names_;
};
class DBBasicTestWithParallelIO : public DBBasicTestMultiGet,
public testing::WithParamInterface<
std::tuple<bool, bool, bool, uint32_t>> {
class DBBasicTestWithParallelIO : public testing::WithParamInterface<
std::tuple<bool, bool, bool, uint32_t>>,
public DBBasicTestMultiGet {
public:
DBBasicTestWithParallelIO()
: DBBasicTestMultiGet("/db_basic_test_with_parallel_io", 1,
+25
View File
@@ -330,6 +330,8 @@ class DBTestSharedWriteBufferAcrossCFs
TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kAll);
options.arena_block_size = 4096;
auto flush_listener = std::make_shared<FlushCounterListener>();
options.listeners.push_back(flush_listener);
@@ -497,6 +499,29 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_.get(), "nikitich"),
static_cast<uint64_t>(2));
}
const uint64_t wbm_flushes =
TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_MANAGER);
ASSERT_GT(wbm_flushes, 0);
EXPECT_EQ(0, TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_FULL));
HistogramData all_memtable_memory;
options.statistics->histogramData(FLUSH_MEMTABLE_MEMORY_BYTES,
&all_memtable_memory);
EXPECT_GE(all_memtable_memory.count, wbm_flushes);
EXPECT_GT(all_memtable_memory.sum, 0);
HistogramData all_memtable_data_size;
options.statistics->histogramData(FLUSH_MEMTABLE_TOTAL_DATA_SIZE,
&all_memtable_data_size);
EXPECT_GE(all_memtable_data_size.count, wbm_flushes);
EXPECT_GT(all_memtable_data_size.sum, 0);
HistogramData wbm_memtable_memory;
options.statistics->histogramData(
FLUSH_WRITE_BUFFER_MANAGER_MEMTABLE_MEMORY_BYTES, &wbm_memtable_memory);
EXPECT_EQ(wbm_flushes, wbm_memtable_memory.count);
EXPECT_GT(wbm_memtable_memory.sum, 0);
if (cost_cache_) {
ASSERT_GE(cache->GetUsage(), 256 * 1024);
Close();
+7 -3
View File
@@ -252,7 +252,7 @@ Status DBImpl::GetLiveFilesStorageInfo(
"capturing live files. Retry with flush enabled.");
}
if (flush_memtable) {
wal_locked = lock_wal_count_ > 0;
wal_locked = !lock_wal_owner_thread_id_counts_.empty();
if (wal_locked) {
if (needs_blob_direct_write_flush) {
mutex_.Unlock();
@@ -260,8 +260,12 @@ Status DBImpl::GetLiveFilesStorageInfo(
"Blob direct write requires flushing active blob files before "
"capturing live files. Retry with WAL unlocked.");
}
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Can't FlushForGetLiveFiles while WAL is locked");
}
if (wal_locked && !has_unpersisted_data_.load(std::memory_order_relaxed)) {
ROCKS_LOG_INFO(
immutable_db_options_.info_log,
"Skipping FlushForGetLiveFiles while WAL is locked and all data is "
"already persisted");
} else {
Status status = FlushForGetLiveFiles(opts.atomic_flush);
if (!status.ok()) {
+67
View File
@@ -473,6 +473,61 @@ TEST_F(DBFlushTest, StatisticsGarbageBasic) {
Close();
}
TEST_F(DBFlushTest, FlushReasonStatsWriteBufferFull) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kAll);
options.create_if_missing = true;
options.compression = kNoCompression;
options.disable_auto_compactions = true;
options.avoid_flush_during_shutdown = true;
options.write_buffer_size = 16 * 1024;
options.max_write_buffer_number = 8;
auto flush_listener = std::make_shared<FlushCounterListener>();
flush_listener->expected_flush_reason = FlushReason::kWriteBufferFull;
options.listeners.push_back(flush_listener);
ASSERT_OK(TryReopen(options));
WriteOptions write_options;
write_options.disableWAL = true;
for (int i = 0; i < 20; ++i) {
ASSERT_OK(Put(Key(i), DummyString(10 * 1024), write_options));
}
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
const uint64_t write_buffer_full_flushes =
TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_FULL);
ASSERT_GT(write_buffer_full_flushes, 0);
EXPECT_EQ(0, TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_MANAGER));
HistogramData all_memtable_memory;
options.statistics->histogramData(FLUSH_MEMTABLE_MEMORY_BYTES,
&all_memtable_memory);
EXPECT_GE(all_memtable_memory.count, write_buffer_full_flushes);
EXPECT_GT(all_memtable_memory.sum, 0);
HistogramData all_memtable_data_size;
options.statistics->histogramData(FLUSH_MEMTABLE_TOTAL_DATA_SIZE,
&all_memtable_data_size);
EXPECT_GE(all_memtable_data_size.count, write_buffer_full_flushes);
EXPECT_GT(all_memtable_data_size.sum, 0);
HistogramData write_buffer_full_memtable_memory;
options.statistics->histogramData(
FLUSH_WRITE_BUFFER_FULL_MEMTABLE_MEMORY_BYTES,
&write_buffer_full_memtable_memory);
EXPECT_EQ(write_buffer_full_flushes, write_buffer_full_memtable_memory.count);
EXPECT_GT(write_buffer_full_memtable_memory.sum, 0);
HistogramData wbm_memtable_memory;
options.statistics->histogramData(
FLUSH_WRITE_BUFFER_MANAGER_MEMTABLE_MEMORY_BYTES, &wbm_memtable_memory);
EXPECT_EQ(0, wbm_memtable_memory.count);
}
TEST_F(DBFlushTest, StatisticsGarbageInsertAndDeletes) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
@@ -2747,6 +2802,8 @@ TEST_P(DBAtomicFlushTest, ManualAtomicFlush) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.atomic_flush = GetParam();
options.statistics = CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kAll);
options.write_buffer_size = (static_cast<size_t>(64) << 20);
auto flush_listener = std::make_shared<FlushCounterListener>();
flush_listener->expected_flush_reason = FlushReason::kManualFlush;
@@ -2773,6 +2830,16 @@ TEST_P(DBAtomicFlushTest, ManualAtomicFlush) {
}
ASSERT_OK(Flush(cf_ids));
EXPECT_EQ(options.atomic_flush ? 1 : 0,
TestGetTickerCount(options, ATOMIC_FLUSH_REQUEST_REASON_OTHER));
EXPECT_EQ(0, TestGetTickerCount(
options, ATOMIC_FLUSH_REQUEST_REASON_WRITE_BUFFER_FULL));
EXPECT_EQ(0, TestGetTickerCount(
options, ATOMIC_FLUSH_REQUEST_REASON_WRITE_BUFFER_MANAGER));
EXPECT_EQ(0, TestGetTickerCount(
options,
ATOMIC_FLUSH_REQUEST_REASON_MEMTABLE_MAX_RANGE_DELETIONS));
for (size_t i = 0; i != num_cfs; ++i) {
auto cfh = static_cast<ColumnFamilyHandleImpl*>(handles_[i]);
ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed());
+32 -15
View File
@@ -334,12 +334,21 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
// FetchAddLastAllocatedSequence() before writes complete, but only
// published via SetLastSequence() after success. If we're recovering from
// an error, there may be allocated-but-not-published sequence numbers.
// We must sync last_sequence_ with last_allocated_sequence_ before creating
// any new memtables/WALs, otherwise the new WAL could start with a sequence
// number lower than what was already written, causing "sequence number
// going backwards" corruption on subsequent recovery.
if (immutable_db_options_.two_write_queues) {
// Start recovery from a published sequence number that covers writers which
// were already in flight. Recovery flushes repeat this sync at the actual
// memtable-switch fence, after FlushAllColumnFamilies() has dropped and
// re-acquired the DB mutex.
if (two_write_queues_) {
WriteThread::Writer w;
write_thread_.EnterUnbatched(&w, &mutex_);
WriteThread::Writer nonmem_w;
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
WaitForPendingWrites();
versions_->SyncLastSequenceWithAllocated();
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
write_thread_.ExitUnbatched(&w);
}
TEST_SYNC_POINT("DBImpl::ResumeImpl:AfterSyncSeq");
@@ -2160,11 +2169,12 @@ Status DBImpl::ApplyWALToManifest(const ReadOptions& read_options,
}
Status DBImpl::LockWAL() {
const uint64_t thread_id = env_->GetThreadID();
{
InstrumentedMutexLock lock(&mutex_);
if (lock_wal_count_ > 0) {
if (!lock_wal_owner_thread_id_counts_.empty()) {
assert(lock_wal_write_token_);
++lock_wal_count_;
++lock_wal_owner_thread_id_counts_[thread_id];
} else {
// NOTE: this will "unnecessarily" wait for other non-LockWAL() write
// stalls to clear before LockWAL returns, however fixing that would
@@ -2182,12 +2192,13 @@ Status DBImpl::LockWAL() {
}
// NOTE: releasing mutex in EnterUnbatched might mean we are actually
// now lock_wal_count > 0
if (lock_wal_count_ == 0) {
// now locked by another thread.
if (lock_wal_owner_thread_id_counts_.empty()) {
assert(!lock_wal_write_token_);
lock_wal_write_token_ = write_controller_.GetStopToken();
}
++lock_wal_count_;
assert(lock_wal_write_token_);
++lock_wal_owner_thread_id_counts_[thread_id];
if (two_write_queues_) {
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
@@ -2198,23 +2209,27 @@ Status DBImpl::LockWAL() {
// NOTE: avoid I/O holding DB mutex
Status s = FlushWAL(/*sync=*/false);
if (!s.ok()) {
// Non-OK return should not be in locked state
// Undo this LockWAL().
UnlockWAL().PermitUncheckedError();
}
return s;
}
Status DBImpl::UnlockWAL() {
const uint64_t thread_id = env_->GetThreadID();
bool signal = false;
uint64_t maybe_stall_begun_count = 0;
uint64_t nonmem_maybe_stall_begun_count = 0;
{
InstrumentedMutexLock lock(&mutex_);
if (lock_wal_count_ == 0) {
return Status::Aborted("No LockWAL() in effect");
const auto owner = lock_wal_owner_thread_id_counts_.find(thread_id);
if (owner == lock_wal_owner_thread_id_counts_.end()) {
return Status::Aborted("No LockWAL() held by current thread");
}
--lock_wal_count_;
if (lock_wal_count_ == 0) {
if (--owner->second == 0) {
lock_wal_owner_thread_id_counts_.erase(owner);
}
if (lock_wal_owner_thread_id_counts_.empty()) {
lock_wal_write_token_.reset();
signal = true;
// For the last UnlockWAL, we don't want to return from UnlockWAL()
@@ -3752,6 +3767,7 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
}
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
key_context.reserve(num_keys);
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
sorted_keys.resize(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
@@ -3942,6 +3958,7 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
}
}
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
key_context.reserve(num_keys);
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
sorted_keys.resize(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
+12 -5
View File
@@ -1215,6 +1215,8 @@ class DBImpl : public DB {
bool TEST_IsRecoveryInProgress();
Status TEST_ResumeImpl(DBRecoverContext context);
// Return the maximum overlapping data (in bytes) at next level for any
// file at a level >= 1.
uint64_t TEST_MaxNextLevelOverlappingBytes(
@@ -2483,6 +2485,10 @@ class DBImpl : public DB {
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {},
bool entered_write_thread = false);
// REQUIRES: mutex locked and write queues drained up to the recovery flush
// fence that is about to switch memtables.
void MaybeSyncLastSequenceWithAllocatedForRecovery(FlushReason flush_reason);
Status RetryFlushesForErrorRecovery(FlushReason flush_reason, bool wait);
// Wait until flushing this column family won't stall writes
@@ -2535,7 +2541,7 @@ class DBImpl : public DB {
}
// Wait for any LockWAL to clear
while (lock_wal_count_ > 0) {
while (!lock_wal_owner_thread_id_counts_.empty()) {
bg_cv_.Wait();
}
}
@@ -3594,12 +3600,13 @@ class DBImpl : public DB {
// Stop write token that is acquired when first LockWAL() is called.
// Destroyed when last UnlockWAL() is called. Controlled by DB mutex.
// See lock_wal_count_
// See lock_wal_owner_thread_id_counts_
std::unique_ptr<WriteControllerToken> lock_wal_write_token_;
// The number of LockWAL called without matching UnlockWAL call.
// See also lock_wal_write_token_
uint32_t lock_wal_count_ = 0;
// Thread IDs of valid LockWAL() owners and their recursive lock counts. Each
// owning thread must call UnlockWAL() the same number of times before writes
// can resume.
std::unordered_map<uint64_t, uint32_t> lock_wal_owner_thread_id_counts_;
};
class GetWithTimestampReadCallback : public ReadCallback {
+63
View File
@@ -35,6 +35,32 @@
namespace ROCKSDB_NAMESPACE {
namespace {
void RecordAtomicFlushRequestReason(Statistics* stats,
FlushReason flush_reason) {
// Keep this aligned with the existing rocksdb.flush.reason.* counters: they
// cover automatic flush triggers used for write-stall debugging. Other
// FlushReason values are counted by the catch-all other ticker.
switch (flush_reason) {
case FlushReason::kWriteBufferFull:
RecordTick(stats, ATOMIC_FLUSH_REQUEST_REASON_WRITE_BUFFER_FULL);
break;
case FlushReason::kWriteBufferManager:
RecordTick(stats, ATOMIC_FLUSH_REQUEST_REASON_WRITE_BUFFER_MANAGER);
break;
case FlushReason::kMemtableMaxRangeDeletions:
RecordTick(stats,
ATOMIC_FLUSH_REQUEST_REASON_MEMTABLE_MAX_RANGE_DELETIONS);
break;
default:
RecordTick(stats, ATOMIC_FLUSH_REQUEST_REASON_OTHER);
break;
}
}
} // namespace
bool DBImpl::EnoughRoomForCompaction(
ColumnFamilyData* cfd, const std::vector<CompactionInputFiles>& inputs,
bool* sfm_reserved_compact_space, LogBuffer* log_buffer) {
@@ -2235,6 +2261,8 @@ Status DBImpl::FlushAllColumnFamilies(const FlushOptions& flush_options,
Status status;
if (immutable_db_options_.atomic_flush || flush_options.force_atomic_flush) {
mutex_.Unlock();
TEST_SYNC_POINT(
"DBImpl::FlushAllColumnFamilies:BeforeAtomicFlushMemTables");
status = AtomicFlushMemTables(flush_options, flush_reason);
if (status.IsColumnFamilyDropped()) {
status = Status::OK();
@@ -2246,6 +2274,7 @@ Status DBImpl::FlushAllColumnFamilies(const FlushOptions& flush_options,
continue;
}
mutex_.Unlock();
TEST_SYNC_POINT("DBImpl::FlushAllColumnFamilies:BeforeFlushMemTable");
status = FlushMemTable(cfd, flush_options, flush_reason);
TEST_SYNC_POINT("DBImpl::FlushAllColumnFamilies:1");
TEST_SYNC_POINT("DBImpl::FlushAllColumnFamilies:2");
@@ -2600,6 +2629,14 @@ void DBImpl::NotifyOnManualFlushScheduled(autovector<ColumnFamilyData*> cfds,
}
}
void DBImpl::MaybeSyncLastSequenceWithAllocatedForRecovery(
FlushReason flush_reason) {
mutex_.AssertHeld();
if (two_write_queues_ && IsRecoveryFlush(flush_reason)) {
versions_->SyncLastSequenceWithAllocated();
}
}
Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
const FlushOptions& flush_options,
FlushReason flush_reason,
@@ -2612,6 +2649,13 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
"Please try again later after writes are resumed";
return Status::TryAgain(oss.str());
}
if (flush_options.wait) {
InstrumentedMutexLock lock(&mutex_);
if (lock_wal_owner_thread_id_counts_.count(env_->GetThreadID()) > 0) {
return Status::Aborted(
"Likely deadlock as the same thread called LockWAL()");
}
}
Status s;
if (!flush_options.allow_write_stall) {
bool flush_needed = true;
@@ -2639,6 +2683,12 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
}
WaitForPendingWrites();
// Recovery may have released `mutex_` after the earlier `ResumeImpl()`
// sync. Refresh sequence state at the actual memtable-switch fence, after
// both write queues are drained and before `SwitchMemtable()` consumes
// `LastSequence()`.
MaybeSyncLastSequenceWithAllocatedForRecovery(flush_reason);
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load() ||
IsRecoveryFlush(flush_reason)) {
s = SwitchMemtable(cfd, &context);
@@ -2764,6 +2814,13 @@ Status DBImpl::AtomicFlushMemTables(
"Please try again later after writes are resumed";
return Status::TryAgain(oss.str());
}
if (flush_options.wait) {
InstrumentedMutexLock lock(&mutex_);
if (lock_wal_owner_thread_id_counts_.count(env_->GetThreadID()) > 0) {
return Status::Aborted(
"Likely deadlock as the same thread called LockWAL()");
}
}
Status s;
autovector<ColumnFamilyData*> candidate_cfds;
if (provided_candidate_cfds.empty()) {
@@ -2828,6 +2885,11 @@ Status DBImpl::AtomicFlushMemTables(
}
WaitForPendingWrites();
// Keep atomic recovery flushes consistent with the single-CF path: the
// sequence sync must happen after both write queues are drained and before
// any recovery memtable switch reads `LastSequence()`.
MaybeSyncLastSequenceWithAllocatedForRecovery(flush_reason);
SelectColumnFamiliesForAtomicFlush(&cfds, candidate_cfds, flush_reason);
// Unref the newly generated candidate cfds (when not provided) in
@@ -3531,6 +3593,7 @@ bool DBImpl::EnqueuePendingFlush(const FlushRequest& flush_req) {
}
++unscheduled_flushes_;
flush_queue_.push_back(flush_req);
RecordAtomicFlushRequestReason(stats_, flush_req.flush_reason);
enqueued = true;
}
return enqueued;
+5
View File
@@ -35,6 +35,11 @@ Status DBImpl::TEST_SwitchWAL() {
return s;
}
Status DBImpl::TEST_ResumeImpl(DBRecoverContext context) {
InstrumentedMutexLock l(&mutex_);
return ResumeImpl(context);
}
uint64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
ColumnFamilyHandle* column_family) {
ColumnFamilyData* cfd;
+2 -4
View File
@@ -1941,10 +1941,8 @@ Status DBImpl::MaybeFlushFinalMemtableOrRestoreActiveLogFiles(
const uint64_t new_min_log = max_wal_number + 1;
VersionEdit wal_deletion;
bool emit_wal_deletion = false;
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
// Determining whether DeleteWalsBefore actually shrinks WalSet
// membership requires WalSet state outside this site, so emit
// unconditionally (pre-existing behavior).
if (immutable_db_options_.track_and_verify_wals_in_manifest &&
new_min_log > versions_->GetWalSet().GetMinWalNumberToKeep()) {
wal_deletion.DeleteWalsBefore(new_min_log);
emit_wal_deletion = true;
}
+22 -5
View File
@@ -3029,12 +3029,23 @@ Status DBImpl::TrimMemtableHistory(WriteContext* context) {
Status DBImpl::ScheduleFlushes(WriteContext* context) {
autovector<ColumnFamilyData*> cfds;
FlushReason atomic_flush_reason = FlushReason::kWriteBufferFull;
if (immutable_db_options_.atomic_flush) {
// Atomic flush has one request-level reason. Derive it only from CFs that
// scheduled the flush, not from every CF selected into the atomic cut.
ColumnFamilyData* trigger_cfd;
while ((trigger_cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
if (!trigger_cfd->mem()->IsEmpty() &&
trigger_cfd->mem()->GetFlushReason() ==
FlushReason::kMemtableMaxRangeDeletions) {
atomic_flush_reason = FlushReason::kMemtableMaxRangeDeletions;
}
trigger_cfd->UnrefAndTryDelete();
}
SelectColumnFamiliesForAtomicFlush(&cfds);
for (auto cfd : cfds) {
cfd->Ref();
}
flush_scheduler_.Clear();
} else {
ColumnFamilyData* tmp_cfd;
while ((tmp_cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
@@ -3050,10 +3061,15 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
TEST_SYNC_POINT_CALLBACK("DBImpl::ScheduleFlushes:PreSwitchMemtable",
nullptr);
autovector<FlushReason> flush_reasons;
flush_reasons.reserve(cfds.size());
for (auto& cfd : cfds) {
FlushReason flush_reason = FlushReason::kWriteBufferFull;
if (status.ok() && !cfd->mem()->IsEmpty()) {
flush_reason = cfd->mem()->GetFlushReason();
status = SwitchMemtable(cfd, context);
}
flush_reasons.push_back(flush_reason);
if (cfd->UnrefAndTryDelete()) {
cfd = nullptr;
}
@@ -3068,12 +3084,13 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
AssignAtomicFlushSeq(cfds);
FlushRequest flush_req;
flush_req.atomic_flush = true;
GenerateFlushRequest(cfds, FlushReason::kWriteBufferFull, &flush_req);
GenerateFlushRequest(cfds, atomic_flush_reason, &flush_req);
EnqueuePendingFlush(flush_req);
} else {
for (auto* cfd : cfds) {
assert(flush_reasons.size() == cfds.size());
for (size_t i = 0; i < cfds.size(); ++i) {
FlushRequest flush_req;
GenerateFlushRequest({cfd}, FlushReason::kWriteBufferFull, &flush_req);
GenerateFlushRequest({cfds[i]}, flush_reasons[i], &flush_req);
EnqueuePendingFlush(flush_req);
}
}
@@ -3102,7 +3119,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
ReadOnlyMemTable* new_imm,
SequenceNumber last_seqno) {
mutex_.AssertHeld();
assert(lock_wal_count_ == 0);
assert(lock_wal_owner_thread_id_counts_.empty());
// TODO: plumb Env::IOActivity, Env::IOPriority
const WriteOptions write_options;
+64
View File
@@ -1921,6 +1921,9 @@ Status DBIter::ValidateScanOptions(const MultiScanArgs& multiscan_opts) const {
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
const bool has_limit = scan_opts.front().range.limit.has_value();
if (multiscan_opts.reverse && !has_limit) {
return Status::InvalidArgument("Reverse MultiScan requires limit");
}
if (!has_limit && scan_opts.size() > 1) {
return Status::InvalidArgument("Scan has no upper bound");
}
@@ -2004,6 +2007,12 @@ void DBIter::Seek(const Slice& target) {
StopWatch sw(clock_, statistics_, DB_SEEK);
if (scan_opts_.has_value()) {
if (scan_opts_->reverse) {
status_ = Status::InvalidArgument("Seek called on reverse MultiScan");
valid_ = false;
return;
}
// Validate the seek target is as expected in the previously prepared range
auto const& scan_ranges = scan_opts_.value().GetScanRanges();
if (scan_index_ >= scan_ranges.size()) {
@@ -2110,6 +2119,61 @@ void DBIter::SeekForPrev(const Slice& target) {
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
StopWatch sw(clock_, statistics_, DB_SEEK);
if (scan_opts_.has_value()) {
if (!scan_opts_->reverse) {
status_ =
Status::InvalidArgument("SeekForPrev called on forward MultiScan");
valid_ = false;
return;
}
auto const& scan_ranges = scan_opts_.value().GetScanRanges();
if (scan_index_ >= scan_ranges.size()) {
status_ = Status::InvalidArgument(
"SeekForPrev called after exhausting all of the scan ranges");
valid_ = false;
return;
}
auto const& range = scan_ranges[scan_index_];
auto const& limit = range.range.limit;
assert(limit.has_value());
if (!limit.has_value() ||
user_comparator_.CompareWithoutTimestamp(target, *limit) != 0) {
status_ = Status::InvalidArgument(
"SeekForPrev target does not match the limit of the next prepared "
"range at index " +
std::to_string(scan_index_));
valid_ = false;
return;
}
auto const& start = range.range.start;
assert(start.has_value());
if (iterate_lower_bound_ == nullptr ||
user_comparator_.CompareWithoutTimestamp(start.value(),
*iterate_lower_bound_) != 0) {
status_ = Status::InvalidArgument(
"Lower bound is not set to the same start value of the next "
"prepared range at index " +
std::to_string(scan_index_));
valid_ = false;
return;
}
if (iterate_upper_bound_ == nullptr ||
user_comparator_.CompareWithoutTimestamp(limit.value(),
*iterate_upper_bound_) != 0) {
status_ = Status::InvalidArgument(
"Upper bound is not set to the same limit value of the next "
"prepared range at index " +
std::to_string(scan_index_));
valid_ = false;
return;
}
scan_index_++;
}
if (has_trace_state_) {
// TODO: What do we do if this returns an error?
Slice lower_bound, upper_bound;
+152 -7
View File
@@ -5418,6 +5418,157 @@ TEST_P(DBMultiScanIteratorTest, AsyncPrefetchAcrossMultipleFiles) {
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, ReversePrefetchAcrossMultipleRanges) {
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(303);
for (int i = 0; i < 1000; ++i) {
ASSERT_OK(Put(Key(i), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 3);
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
auto tracking_dispatcher = std::make_shared<TrackingIODispatcher>();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false;
scan_options.reverse = true;
scan_options.io_dispatcher = tracking_dispatcher;
std::vector<std::string> key_ranges(
{Key(100), Key(200), Key(500), Key(600), Key(800), Key(900)});
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
std::vector<std::string> actual_keys;
try {
for (auto range : *iter) {
for (auto it : range) {
actual_keys.push_back(it.first.ToString());
}
}
} catch (MultiScanException& ex) {
FAIL() << "Iterator returned status " << ex.what();
} catch (std::logic_error& ex) {
FAIL() << "Iterator returned logic error " << ex.what();
}
std::vector<std::string> expected_keys;
for (int i = 199; i >= 100; --i) {
expected_keys.push_back(Key(i));
}
for (int i = 599; i >= 500; --i) {
expected_keys.push_back(Key(i));
}
for (int i = 899; i >= 800; --i) {
expected_keys.push_back(Key(i));
}
ASSERT_EQ(actual_keys, expected_keys);
ASSERT_GT(tracking_dispatcher->GetReadSets().size(), 0);
}
TEST_P(DBMultiScanIteratorTest, ReverseUnreleasedPrefetchBlocksCountAsWasted) {
auto options = CurrentOptions();
options.compression = kNoCompression;
options.disable_auto_compactions = true;
options.statistics = CreateDBStatistics();
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
table_options.block_cache = NewLRUCache(10 * 1024 * 1024);
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
constexpr int kNumKeys = 40;
const std::string value(100, 'v');
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
auto tracking_dispatcher = std::make_shared<TrackingIODispatcher>();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = false;
scan_options.reverse = true;
scan_options.io_dispatcher = tracking_dispatcher;
const std::string start_key = Key(0);
const std::string limit_key = Key(kNumKeys);
scan_options.insert(start_key, limit_key);
{
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
try {
auto range_it = iter->begin();
ASSERT_NE(range_it, iter->end());
auto range = *range_it;
auto row_it = range.begin();
ASSERT_NE(row_it, range.end());
ASSERT_EQ((*row_it).first.ToString(), Key(kNumKeys - 1));
} catch (MultiScanException& ex) {
FAIL() << "Iterator returned status " << ex.what();
} catch (std::logic_error& ex) {
FAIL() << "Iterator returned logic error " << ex.what();
}
ASSERT_GT(tracking_dispatcher->GetReadSets().size(), 0);
}
ASSERT_GT(
options.statistics->getTickerCount(MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
}
TEST_P(DBMultiScanIteratorTest, ReverseRequiresLimits) {
auto options = CurrentOptions();
options.compression = kNoCompression;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Put("b", "vb"));
ASSERT_OK(Flush());
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.reverse = true;
scan_options.insert("a");
scan_options.insert("b", "c");
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
try {
(void)iter->begin();
FAIL() << "Expected reverse MultiScan without an upper bound to fail";
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
ASSERT_TRUE(ex.status().IsInvalidArgument());
}
}
// Wrapper filesystem that does not support async IO.
// Used to verify that MultiScan gracefully falls back to sync IO.
class NoAsyncIOFS : public FileSystemWrapper {
@@ -6114,13 +6265,7 @@ TEST_P(DBMultiScanIteratorTest, WastedBlocksTracking) {
uint64_t wasted =
options.statistics->getTickerCount(MULTISCAN_PREFETCH_BLOCKS_WASTED);
// We expect some wasted blocks due to the gap between ranges
// The exact number depends on prefetch behavior, but should be > 0
// if blocks between k020-k050 were prefetched
std::cout << "Wasted blocks: " << wasted << std::endl;
// Note: The test verifies the tracking mechanism works.
// The actual count depends on prefetch heuristics which may vary.
ASSERT_GT(wasted, 0);
}
class ReadPathRangeTombstoneTest : public DBIteratorBaseTest,
+73
View File
@@ -3559,6 +3559,79 @@ TEST_F(DBRangeDelTest, MemtableMaxRangeDeletions) {
ASSERT_EQ(3, NumTableFilesAtLevel(0));
}
TEST_F(DBRangeDelTest, FlushReasonStatsMemtableMaxRangeDeletions) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kAll);
options.disable_auto_compactions = true;
options.memtable_max_range_deletions = 1;
auto flush_listener = std::make_shared<FlushCounterListener>();
flush_listener->expected_flush_reason =
FlushReason::kMemtableMaxRangeDeletions;
options.listeners.push_back(flush_listener);
DestroyAndReopen(options);
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0),
Key(10)));
// The next write observes the pending flush request.
ASSERT_OK(Put(Key(0), "value"));
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
const uint64_t range_delete_flushes =
TestGetTickerCount(options, FLUSH_REASON_MEMTABLE_MAX_RANGE_DELETIONS);
ASSERT_GT(range_delete_flushes, 0);
EXPECT_EQ(0, TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_FULL));
EXPECT_EQ(0, TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_MANAGER));
EXPECT_EQ(0, TestGetTickerCount(
options,
ATOMIC_FLUSH_REQUEST_REASON_MEMTABLE_MAX_RANGE_DELETIONS));
EXPECT_EQ(0, TestGetTickerCount(options, ATOMIC_FLUSH_REQUEST_REASON_OTHER));
EXPECT_EQ(range_delete_flushes,
static_cast<uint64_t>(flush_listener->count.load()));
}
TEST_F(DBRangeDelTest, AtomicFlushReasonStatsMemtableMaxRangeDeletions) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kAll);
options.atomic_flush = true;
options.disable_auto_compactions = true;
options.memtable_max_range_deletions = 1;
auto flush_listener = std::make_shared<FlushCounterListener>();
flush_listener->expected_flush_reason =
FlushReason::kMemtableMaxRangeDeletions;
options.listeners.push_back(flush_listener);
CreateAndReopenWithCF({"pikachu"}, options);
ASSERT_OK(Put(1, Key(0), "value"));
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0),
Key(10)));
// The next write observes the pending flush request.
ASSERT_OK(Put(Key(0), "value"));
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
const uint64_t range_delete_flushes =
TestGetTickerCount(options, FLUSH_REASON_MEMTABLE_MAX_RANGE_DELETIONS);
ASSERT_GE(range_delete_flushes, 2);
EXPECT_EQ(1, TestGetTickerCount(
options,
ATOMIC_FLUSH_REQUEST_REASON_MEMTABLE_MAX_RANGE_DELETIONS));
EXPECT_EQ(0, TestGetTickerCount(options, FLUSH_REASON_WRITE_BUFFER_FULL));
EXPECT_EQ(0, TestGetTickerCount(
options, ATOMIC_FLUSH_REQUEST_REASON_WRITE_BUFFER_FULL));
EXPECT_EQ(0, TestGetTickerCount(
options, ATOMIC_FLUSH_REQUEST_REASON_WRITE_BUFFER_MANAGER));
EXPECT_EQ(0, TestGetTickerCount(options, ATOMIC_FLUSH_REQUEST_REASON_OTHER));
EXPECT_EQ(range_delete_flushes,
static_cast<uint64_t>(flush_listener->count.load()));
}
TEST_F(DBRangeDelTest, RangeDelReseekAfterFileReadError) {
// This is to test a bug that is fixed in
// https://github.com/facebook/rocksdb/pull/11786.
+22 -26
View File
@@ -782,19 +782,30 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
ASSERT_OK(db_->GetLiveFilesStorageInfo({lf_opts}, &files));
}
port::Thread parallel_lock_wal{[&]() {
ASSERT_OK(db_->LockWAL()); // 2 -> 3 or 1 -> 2
}};
SyncPoint::GetInstance()->LoadDependency(
{{"DBWriteTest::LockWALConcurrentRecursive:ParallelLockAcquired",
"DBWriteTest::LockWALConcurrentRecursive:MainUnlocks"},
{"DBWriteTest::LockWALConcurrentRecursive:MainAllowsParallelUnlock",
"DBWriteTest::LockWALConcurrentRecursive:ParallelUnlocks"}});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(db_->UnlockWAL()); // 2 -> 1 or 3 -> 2
port::Thread parallel_lock_wal{[&]() {
ASSERT_OK(db_->LockWAL()); // 2 -> 3
TEST_SYNC_POINT(
"DBWriteTest::LockWALConcurrentRecursive:ParallelLockAcquired");
TEST_SYNC_POINT("DBWriteTest::LockWALConcurrentRecursive:ParallelUnlocks");
ASSERT_OK(db_->UnlockWAL());
}};
TEST_SYNC_POINT("DBWriteTest::LockWALConcurrentRecursive:MainUnlocks");
ASSERT_OK(db_->UnlockWAL()); // 3 -> 2
// Give parallel_put an extra chance to jump in case of bug
std::this_thread::yield();
parallel_lock_wal.join();
ASSERT_FALSE(parallel_put_completed.Load());
ASSERT_FALSE(parallel_ingest_completed.Load());
ASSERT_FALSE(flush_completed.Load());
// Should now have 2 outstanding LockWAL
// Should still have 2 outstanding LockWAL().
ASSERT_EQ(Get("k1"), "k1_orig");
ASSERT_OK(db_->UnlockWAL()); // 2 -> 1
@@ -807,26 +818,11 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
ASSERT_EQ(Get("k2"), "NOT_FOUND");
ASSERT_EQ(frozen_seqno, db_->GetLatestSequenceNumber());
// Ensure final Unlock is concurrency safe and extra Unlock is safe but
// non-OK
std::atomic<int> unlock_ok{0};
port::Thread parallel_stuff{[&]() {
if (db_->UnlockWAL().ok()) {
unlock_ok++;
}
ASSERT_OK(db_->LockWAL());
if (db_->UnlockWAL().ok()) {
unlock_ok++;
}
}};
if (db_->UnlockWAL().ok()) {
unlock_ok++;
}
parallel_stuff.join();
// There was one extra unlock, so just one non-ok
ASSERT_EQ(unlock_ok.load(), 2);
TEST_SYNC_POINT(
"DBWriteTest::LockWALConcurrentRecursive:MainAllowsParallelUnlock");
parallel_lock_wal.join();
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->LoadDependency({});
// Write can proceed
parallel_put.join();
+18 -3
View File
@@ -33,6 +33,7 @@
#include "logging/logging.h"
#include "monitoring/iostats_context_imp.h"
#include "monitoring/perf_context_imp.h"
#include "monitoring/statistics_impl.h"
#include "monitoring/thread_status_util.h"
#include "port/port.h"
#include "rocksdb/db.h"
@@ -82,6 +83,8 @@ const char* GetFlushReasonString(FlushReason flush_reason) {
return "WAL Full";
case FlushReason::kCatchUpAfterErrorRecovery:
return "Catch Up After Error Recovery";
case FlushReason::kMemtableMaxRangeDeletions:
return "Memtable Max Range Deletions";
default:
return "Invalid";
}
@@ -959,9 +962,21 @@ Status FlushJob::WriteLevel0Table() {
total_num_range_deletes += m->NumRangeDeletion();
}
// TODO(cbi): when memtable is flushed due to number of range deletions
// hitting limit memtable_max_range_deletions, flush_reason_ is still
// "Write Buffer Full", should make update flush_reason_ accordingly.
RecordInHistogram(stats_, FLUSH_MEMTABLE_MEMORY_BYTES, total_memory_usage);
RecordInHistogram(stats_, FLUSH_MEMTABLE_TOTAL_DATA_SIZE, total_data_size);
if (flush_reason_ == FlushReason::kWriteBufferFull) {
RecordTick(stats_, FLUSH_REASON_WRITE_BUFFER_FULL);
RecordInHistogram(stats_, FLUSH_WRITE_BUFFER_FULL_MEMTABLE_MEMORY_BYTES,
total_memory_usage);
} else if (flush_reason_ == FlushReason::kWriteBufferManager) {
RecordTick(stats_, FLUSH_REASON_WRITE_BUFFER_MANAGER);
RecordInHistogram(stats_,
FLUSH_WRITE_BUFFER_MANAGER_MEMTABLE_MEMORY_BYTES,
total_memory_usage);
} else if (flush_reason_ == FlushReason::kMemtableMaxRangeDeletions) {
RecordTick(stats_, FLUSH_REASON_MEMTABLE_MAX_RANGE_DELETIONS);
}
event_logger_->Log() << "job" << job_context_->job_id << "event"
<< "flush_started" << "num_memtables" << mems_.size()
<< "total_num_input_entries" << total_num_input_entries
+9
View File
@@ -390,6 +390,15 @@ bool MemTable::ShouldFlushNow() {
return arena_.AllocatedAndUnused() < kArenaBlockSize / 4;
}
FlushReason MemTable::GetFlushReason() const {
if (memtable_max_range_deletions_ > 0 &&
num_range_deletes_.LoadRelaxed() >=
static_cast<uint64_t>(memtable_max_range_deletions_)) {
return FlushReason::kMemtableMaxRangeDeletions;
}
return FlushReason::kWriteBufferFull;
}
void MemTable::UpdateFlushState() {
auto state = flush_state_.load(std::memory_order_relaxed);
if (state == FLUSH_NOT_REQUESTED && ShouldFlushNow()) {
+4
View File
@@ -858,6 +858,10 @@ class MemTable final : public ReadOnlyMemTable {
// Returns a heuristic flush decision
bool ShouldFlushNow();
// Returns the reason for a flush scheduled because this memtable decided it
// should stop accepting more writes.
FlushReason GetFlushReason() const;
// Updates `fragmented_range_tombstone_list_` that will be used to serve reads
// when this memtable becomes an immutable memtable (in some
// MemtableListVersion::memlist_). Should be called when this memtable is
+104 -29
View File
@@ -3,14 +3,56 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include <string>
#include <vector>
#include "file/file_util.h"
#include "rocksdb/db.h"
#include "rocksdb/file_system.h"
#include "rocksdb/iterator.h"
namespace ROCKSDB_NAMESPACE {
using MultiScanIterator = MultiScan::MultiScanIterator;
namespace {
Status ValidateScanOptionsForMultiScan(const MultiScanArgs& multiscan_opts) {
if (multiscan_opts.empty()) {
return Status::InvalidArgument("Empty MultiScanArgs");
}
const Comparator* comparator = multiscan_opts.GetComparator();
if (comparator == nullptr) {
return Status::InvalidArgument("MultiScanArgs requires comparator");
}
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
for (size_t i = 0; i < scan_opts.size(); ++i) {
const auto& scan_range = scan_opts[i].range;
if (!scan_range.start.has_value()) {
return Status::InvalidArgument("Scan has no start key at index " +
std::to_string(i));
}
if (multiscan_opts.reverse && !scan_range.limit.has_value()) {
return Status::InvalidArgument(
"Reverse MultiScan requires limit at index " + std::to_string(i));
}
if (scan_range.limit.has_value() &&
comparator->CompareWithoutTimestamp(
scan_range.start.value(), /*a_has_ts=*/false,
scan_range.limit.value(), /*b_has_ts=*/false) >= 0) {
return Status::InvalidArgument(
"Scan start key is large or equal than limit at index " +
std::to_string(i));
}
}
return Status::OK();
}
} // namespace
MultiScan::MultiScan(const ReadOptions& read_options,
const MultiScanArgs& scan_opts, DB* db,
ColumnFamilyHandle* cfh)
@@ -28,14 +70,28 @@ MultiScan::MultiScan(const ReadOptions& read_options,
}()),
db_(db),
cfh_(cfh) {
Status validate_status = ValidateScanOptionsForMultiScan(scan_opts_);
if (!validate_status.ok()) {
db_iter_.reset(NewErrorIterator(validate_status));
return;
}
bool slow_path = false;
// Setup read_options with iterate_uuper_bound based on the first scan.
// Subsequent scans will update and allocate a new DB iterator as necessary
if (scan_opts_.GetScanRanges()[0].range.limit) {
upper_bound_ = *scan_opts_.GetScanRanges()[0].range.limit;
read_options_.iterate_upper_bound = &upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
// Setup read_options bounds based on the first scan. Subsequent scans will
// update the bounds and allocate a new DB iterator as necessary.
{
const ScanOptions& first_scan = scan_opts_.GetScanRanges()[0];
if (scan_opts_.reverse) {
lower_bound_ = *first_scan.range.start;
read_options_.iterate_lower_bound = &lower_bound_;
upper_bound_ = *first_scan.range.limit;
read_options_.iterate_upper_bound = &upper_bound_;
} else if (first_scan.range.limit) {
upper_bound_ = *first_scan.range.limit;
read_options_.iterate_upper_bound = &upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
}
for (const auto& opts : scan_opts_.GetScanRanges()) {
// Check that all the ScanOptions either specify an upper bound or not. If
@@ -54,36 +110,55 @@ MultiScan::MultiScan(const ReadOptions& read_options,
}
}
void MultiScanIterator::SeekCurrentRange() {
const ScanOptions& scan_opt = scan_opts_[idx_];
if (scan_opt.range.limit) {
*upper_bound_ = *scan_opt.range.limit;
read_options_.iterate_upper_bound = upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
if (reverse_) {
*lower_bound_ = *scan_opt.range.start;
read_options_.iterate_lower_bound = lower_bound_;
assert(scan_opt.range.limit.has_value());
db_iter_->SeekForPrev(*scan_opt.range.limit);
} else {
db_iter_->Seek(*scan_opt.range.start);
}
}
MultiScanIterator& MultiScanIterator::operator++() {
status_ = db_iter_->status();
if (!status_.ok()) {
throw MultiScanException(status_);
}
if (idx_ >= scan_opts_.size()) {
throw std::logic_error("Index out of range");
if (!valid_) {
throw std::logic_error("Incrementing exhausted MultiScan iterator");
}
idx_++;
if (idx_ < scan_opts_.size()) {
// Check if we need to update read_options_
if (scan_opts_[idx_].range.limit.has_value() !=
(read_options_.iterate_upper_bound != nullptr)) {
if (scan_opts_[idx_].range.limit) {
*upper_bound_ = *scan_opts_[idx_].range.limit;
read_options_.iterate_upper_bound = upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
db_iter_.reset(db_->NewIterator(read_options_, cfh_));
scan_.Reset(db_iter_.get());
} else if (scan_opts_[idx_].range.limit) {
*upper_bound_ = *scan_opts_[idx_].range.limit;
}
db_iter_->Seek(*scan_opts_[idx_].range.start);
status_ = db_iter_->status();
if (!status_.ok()) {
throw MultiScanException(status_);
++idx_;
if (idx_ >= scan_opts_.size()) {
valid_ = false;
return *this;
}
// Check if we need to update read_options_
if (scan_opts_[idx_].range.limit.has_value() !=
(read_options_.iterate_upper_bound != nullptr)) {
if (scan_opts_[idx_].range.limit) {
read_options_.iterate_upper_bound = upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
db_iter_.reset(db_->NewIterator(read_options_, cfh_));
scan_.Reset(db_iter_.get(), reverse_);
}
SeekCurrentRange();
status_ = db_iter_->status();
if (!status_.ok()) {
throw MultiScanException(status_);
}
return *this;
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
// 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).
+15 -13
View File
@@ -179,20 +179,22 @@ Status TableCache::GetTableReader(
} else {
expected_unique_id = kNullUniqueId64x2; // null ID == no verification
}
TableReaderOptions table_reader_options(
ioptions_, mutable_cf_options.prefix_extractor,
mutable_cf_options.compression_manager.get(), file_options,
internal_comparator, mutable_cf_options.block_protection_bytes_per_key,
skip_filters, immortal_tables_, false /* force_direct_prefetch */,
level, block_cache_tracer_, max_file_size_for_l0_meta_pin,
db_session_id_, file_meta.fd.GetNumber(), expected_unique_id,
file_meta.fd.largest_seqno, file_meta.tail_size,
file_meta.user_defined_timestamps_persisted,
avoid_shared_metadata_cache);
// Route same-file ("embedded") blob reads through the CFD's BlobSource for
// caching + stats. nullptr in non-DB contexts (e.g. repair).
table_reader_options.blob_source = blob_source_;
s = mutable_cf_options.table_factory->NewTableReader(
ro,
TableReaderOptions(
ioptions_, mutable_cf_options.prefix_extractor,
mutable_cf_options.compression_manager.get(), file_options,
internal_comparator,
mutable_cf_options.block_protection_bytes_per_key, skip_filters,
immortal_tables_, false /* force_direct_prefetch */, level,
block_cache_tracer_, max_file_size_for_l0_meta_pin, db_session_id_,
file_meta.fd.GetNumber(), expected_unique_id,
file_meta.fd.largest_seqno, file_meta.tail_size,
file_meta.user_defined_timestamps_persisted,
avoid_shared_metadata_cache),
std::move(file_reader), file_meta.fd.GetFileSize(), table_reader,
ro, table_reader_options, std::move(file_reader),
file_meta.fd.GetFileSize(), table_reader,
prefetch_index_and_filter_in_cache);
TEST_SYNC_POINT("TableCache::GetTableReader:0");
}
+13
View File
@@ -34,6 +34,7 @@ class Arena;
struct FileDescriptor;
class GetContext;
class HistogramImpl;
class BlobSource;
struct TableCacheOpenOptions {
// Open a new TableReader owned by the returned iterator instead of reusing
@@ -69,6 +70,15 @@ class TableCache {
const std::string& db_session_id, bool fast_sst_open = false);
~TableCache();
// Sets the BlobSource used to route same-file ("embedded") blob reads of SSTs
// opened through this cache via the blob value cache + BLOB_DB_* statistics.
// Both the TableCache and the BlobSource are owned by the same
// ColumnFamilyData and share its lifetime, so the raw pointer is safe.
// Called once at CFD setup, after the BlobSource is constructed. Left unset
// (nullptr) in non-DB contexts (e.g. repair), where embedded reads fall back
// to a direct (uncached) read.
void SetBlobSource(BlobSource* blob_source) { blob_source_ = blob_source; }
// Cache interface for table cache
using CacheInterface =
BasicTypedCacheInterface<TableReader, CacheEntryRole::kMisc>;
@@ -353,6 +363,9 @@ class TableCache {
Striped<CacheAlignedWrapper<port::Mutex>> loader_mutex_;
std::shared_ptr<IOTracer> io_tracer_;
std::string db_session_id_;
// Owned by the same ColumnFamilyData; see SetBlobSource(). nullptr in non-DB
// contexts (e.g. repair).
BlobSource* blob_source_ = nullptr;
};
} // namespace ROCKSDB_NAMESPACE
+409 -118
View File
@@ -15,6 +15,7 @@
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <thread>
@@ -36,11 +37,46 @@
#include "port/port.h"
#include "table/table_reader.h"
#include "test_util/sync_point.h"
#include "util/cast_util.h"
#include "util/hash_containers.h"
#include "util/string_util.h"
namespace ROCKSDB_NAMESPACE {
class VersionBuilder::Rep {
// Scalar/flag state that a VersionEdit can mutate while being applied. Factored
// into a small struct so the single-edit undo (VersionBuilder::Rep::EditUndo)
// can snapshot and restore all of it with a single copy/swap, and so the field
// list is defined once rather than duplicated between the live state and its
// undo record. VersionBuilder::Rep inherits this (as does EditUndo) so the
// scalar state can be snapshotted/restored via up_cast<MutableScalars> with a
// single copy/swap; Rep is a file-local implementation class, so exposing the
// base is inconsequential.
struct MutableScalars {
// Whether there are invalid new files or invalid deletions on levels larger
// than num_levels_.
bool has_invalid_levels_ = false;
// The fields below are only meaningful when track_found_and_missing_files_ is
// enabled.
// The highest file number among all missing blob files, or
// kInvalidBlobFileNumber if none are missing. Useful to check whether a
// complete Version is available.
uint64_t missing_blob_files_high_ = kInvalidBlobFileNumber;
// Cached result of the last validity check: true if all the files making up
// the Version can be found. Or, when allow_incomplete_valid_version_ is true
// and the version was never edited in an atomic group, true if only a suffix
// of L0 SST files and their associated blob files are missing.
bool valid_version_available_ = false;
// True if the version was ever edited in an atomic group.
bool edited_in_atomic_group_ = false;
// True if the Version was updated since the last validity check, so the
// cached `valid_version_available_` must be recomputed on the next check
// rather than reused.
bool version_updated_since_last_check_ = false;
};
class VersionBuilder::Rep : public MutableScalars {
class NewestFirstBySeqNo {
public:
bool operator()(const FileMetaData* lhs, const FileMetaData* rhs) const {
@@ -265,17 +301,13 @@ class VersionBuilder::Rep {
// on invalid levels. The version is not consistent if in the end the files
// on invalid levels don't cancel out.
std::unordered_map<int, size_t> invalid_level_sizes_;
// Whether there are invalid new files or invalid deletion on levels larger
// than num_levels_.
bool has_invalid_levels_;
// Current levels of table files affected by additions/deletions.
std::unordered_map<uint64_t, int> table_file_levels_;
// Current compact cursors that should be changed after the last compaction
std::unordered_map<int, InternalKey> updated_compact_cursors_;
const std::shared_ptr<const NewestFirstByEpochNumber>
level_zero_cmp_by_epochno_;
const std::shared_ptr<const NewestFirstBySeqNo> level_zero_cmp_by_seqno_;
const std::shared_ptr<const BySmallestKey> level_nonzero_cmp_;
const NewestFirstByEpochNumber level_zero_cmp_by_epochno_;
const NewestFirstBySeqNo level_zero_cmp_by_seqno_;
const BySmallestKey level_nonzero_cmp_;
// Mutable metadata objects for all blob files affected by the series of
// version edits.
@@ -302,30 +334,88 @@ class VersionBuilder::Rep {
std::unordered_set<uint64_t> non_l0_missing_files_;
// Intermediate SST files (blob files not included yet)
std::vector<std::string> intermediate_files_;
// The highest file number for all the missing blob files, useful to check
// if a complete Version is available.
uint64_t missing_blob_files_high_ = kInvalidBlobFileNumber;
// Missing blob files, useful to check if only the missing L0 files'
// associated blob files are missing.
std::unordered_set<uint64_t> missing_blob_files_;
// True if all files consisting the Version can be found. Or if
// `allow_incomplete_valid_version_` is true and the version history is not
// ever edited in an atomic group, this will be true if only a
// suffix of L0 SST files and their associated blob files are missing.
bool valid_version_available_;
// True if version is ever edited in an atomic group.
bool edited_in_atomic_group_;
// Flag to indicate if the Version is updated since last validity check. If no
// `Apply` call is made between a `Rep`'s construction and a
// `ValidVersionAvailable` check or between two `ValidVersionAvailable` calls.
// This flag will be true to indicate the cached validity value can be
// directly used without a recheck.
bool version_updated_since_last_check_;
// Note: additional scalar/flag state mutated while applying edits
// (has_invalid_levels_, missing_blob_files_high_, valid_version_available_,
// edited_in_atomic_group_, version_updated_since_last_check_) lives in the
// MutableScalars base so the single-edit undo can snapshot/restore it in one
// copy/swap.
// End of fields that are only tracked when `track_found_and_missing_files_`
// is enabled.
// Records how to undo (and redo) the most recent `Apply()` so that
// VersionEditHandlerPointInTime can build a `Version` reflecting the state
// *before* an edit (on a valid->invalid negative edge) without copying the
// whole `Rep` on every edit. Only populated when
// `track_found_and_missing_files_` is true.
//
// Each recorded map/set entry stores the "other side" value (the value not
// currently live). Applying `ToggleUndo()` swaps every recorded slot with the
// live state, so it is self-inverse: one call rolls back to the pre-edit
// state, a second call redoes to the post-edit state. Objects momentarily
// removed from the live state (e.g. blob metadata) are held inside these
// records via value/shared_ptr, so nothing referenced by the pre-edit state
// is destroyed during the tentative window. `FileMetaData` removed from
// `added_files` by a deletion is instead kept alive via `deferred_unref` and
// only released by `CommitLastApply()`.
struct EditUndo : MutableScalars {
// True while the live state has been rolled back to the pre-edit state
// (i.e. `ToggleUndo()` has run an odd number of times).
bool rolled_back = false;
// Each map below records, for the keys the edit touches, the value on the
// "other side" (the value not currently live): before the edit while in the
// forward/post-edit state, and after the edit once rolled back. `nullopt`
// means the key is absent on that side. `ToggleUndo()` swaps each recorded
// entry with the corresponding live container, so it is self-inverse.
// levels_[level].added_files entries, keyed by (level, file number). Holds
// the FileMetaData* on the non-live side (the map only owns the pointer,
// not the object; see `deferred_unref` for lifetime of deleted files).
std::map<std::pair<int, uint64_t>, std::optional<FileMetaData*>>
added_files;
// levels_[level].deleted_files membership, keyed by (level, file number);
// value is whether the file number is present on the non-live side.
std::map<std::pair<int, uint64_t>, bool> deleted_files;
// table_file_levels_ entries, keyed by file number (level, or absent).
UnorderedMap<uint64_t, std::optional<int>> table_file_levels;
// invalid_level_sizes_ entries, keyed by level (count, or absent).
UnorderedMap<int, std::optional<size_t>> invalid_level_sizes;
// updated_compact_cursors_ entries, keyed by level (cursor key, or absent).
UnorderedMap<int, std::optional<InternalKey>> compact_cursors;
// mutable_blob_file_metas_ entries, keyed by blob file number. Holds the
// MutableBlobFileMetaData by value on the non-live side; because it owns a
// shared_ptr to the SharedBlobFileMetaData, a rolled-back blob addition is
// kept alive here rather than destroyed (which would spuriously mark the
// blob obsolete).
UnorderedMap<uint64_t, std::optional<MutableBlobFileMetaData>> blob_metas;
// Membership on the non-live side for each tracking set, keyed by file
// number (blob file number for `missing_blob_files`).
UnorderedMap<uint64_t, bool> found_files;
UnorderedMap<uint64_t, bool> l0_missing_files;
UnorderedMap<uint64_t, bool> non_l0_missing_files;
UnorderedMap<uint64_t, bool> missing_blob_files;
// `intermediate_files_` is append-only within an `Apply()`. Restore by
// truncating to `intermediate_files_size` (rollback) / re-appending
// `intermediate_tail` (redo).
size_t intermediate_files_size = 0;
std::vector<std::string> intermediate_tail;
// (Scalar/flag state on the non-live side is held in the MutableScalars
// base subobject.)
// `FileMetaData` removed from `added_files` by a deletion in this edit. The
// ref is held here (not dropped) until `CommitLastApply()`, so a rollback
// can reinstall the file.
std::vector<FileMetaData*> deferred_unref;
};
std::unique_ptr<EditUndo> undo_;
public:
Rep(const FileOptions& file_options, const ImmutableCFOptions* ioptions,
TableCache* table_cache, VersionStorageInfo* base_vstorage,
@@ -339,12 +429,9 @@ class VersionBuilder::Rep {
base_vstorage_(base_vstorage),
version_set_(version_set),
num_levels_(base_vstorage->num_levels()),
has_invalid_levels_(false),
level_zero_cmp_by_epochno_(
std::make_shared<NewestFirstByEpochNumber>()),
level_zero_cmp_by_seqno_(std::make_shared<NewestFirstBySeqNo>()),
level_nonzero_cmp_(std::make_shared<BySmallestKey>(
base_vstorage_->InternalComparator())),
level_zero_cmp_by_epochno_(),
level_zero_cmp_by_seqno_(),
level_nonzero_cmp_(base_vstorage_->InternalComparator()),
file_metadata_cache_res_mgr_(file_metadata_cache_res_mgr),
cfd_(cfd),
version_edit_handler_(version_edit_handler),
@@ -367,52 +454,17 @@ class VersionBuilder::Rep {
}
}
Rep(const Rep& other)
: file_options_(other.file_options_),
ioptions_(other.ioptions_),
table_cache_(other.table_cache_),
base_vstorage_(other.base_vstorage_),
version_set_(other.version_set_),
num_levels_(other.num_levels_),
invalid_level_sizes_(other.invalid_level_sizes_),
has_invalid_levels_(other.has_invalid_levels_),
table_file_levels_(other.table_file_levels_),
updated_compact_cursors_(other.updated_compact_cursors_),
level_zero_cmp_by_epochno_(other.level_zero_cmp_by_epochno_),
level_zero_cmp_by_seqno_(other.level_zero_cmp_by_seqno_),
level_nonzero_cmp_(other.level_nonzero_cmp_),
mutable_blob_file_metas_(other.mutable_blob_file_metas_),
file_metadata_cache_res_mgr_(other.file_metadata_cache_res_mgr_),
cfd_(other.cfd_),
version_edit_handler_(other.version_edit_handler_),
track_found_and_missing_files_(other.track_found_and_missing_files_),
allow_incomplete_valid_version_(other.allow_incomplete_valid_version_),
found_files_(other.found_files_),
l0_missing_files_(other.l0_missing_files_),
non_l0_missing_files_(other.non_l0_missing_files_),
intermediate_files_(other.intermediate_files_),
missing_blob_files_high_(other.missing_blob_files_high_),
missing_blob_files_(other.missing_blob_files_),
valid_version_available_(other.valid_version_available_),
edited_in_atomic_group_(other.edited_in_atomic_group_),
version_updated_since_last_check_(
other.version_updated_since_last_check_) {
assert(ioptions_);
levels_ = new LevelState[num_levels_];
for (int level = 0; level < num_levels_; level++) {
levels_[level] = other.levels_[level];
const auto& added = levels_[level].added_files;
for (auto& pair : added) {
RefFile(pair.second);
}
}
if (track_found_and_missing_files_) {
assert(cfd_);
assert(version_edit_handler_);
}
}
// Rep is only ever held via std::unique_ptr and is not copied. (The previous
// save-point mechanism deep-copied Rep on every edit, which was quadratic;
// it has been replaced by single-edit undo, see EditUndo above.)
Rep(const Rep&) = delete;
Rep& operator=(const Rep&) = delete;
~Rep() {
// Release any FileMetaData whose deletion was deferred by an uncommitted
// Apply(), and ensure the live state is in the forward (post-edit) position
// so the loop below unrefs exactly the files still owned by added_files.
CommitLastApply();
for (int level = 0; level < num_levels_; level++) {
const auto& added = levels_[level].added_files;
for (auto& pair : added) {
@@ -423,12 +475,6 @@ class VersionBuilder::Rep {
delete[] levels_;
}
void RefFile(FileMetaData* f) {
assert(f);
assert(f->refs > 0);
f->refs++;
}
void UnrefFile(FileMetaData* f) {
f->refs--;
if (f->refs <= 0) {
@@ -439,6 +485,15 @@ class VersionBuilder::Rep {
f->fd.pinned_reader.Release(table_cache_->get_cache().get());
}
// Evict from table cache to prevent leaked entries for files whose
// last reference is held by the VersionBuilder (e.g., files from a
// failed LogAndApply that were never installed in any Version).
// Release() above only releases the pinned handle's ref but does not
// remove the entry from the cache hash table.
if (table_cache_ != nullptr) {
TableCache::Evict(table_cache_->get_cache().get(), f->fd.GetNumber());
}
if (file_metadata_cache_res_mgr_) {
Status s = file_metadata_cache_res_mgr_->UpdateCacheReservation(
f->ApproximateMemoryUsage(), false /* increase */);
@@ -448,6 +503,252 @@ class VersionBuilder::Rep {
}
}
//======== Single-edit undo support (track_found_and_missing_files_) =======//
// See the EditUndo declaration above for the overall model.
// Records the pre-edit value of `map[key]` into `saved` (first-touch only, so
// repeated touches of the same key within one edit keep the true pre-edit
// value). The stored value is the "other side" for ToggleMap().
template <typename Map, typename Saved, typename K>
static void RecordMapEntry(const Map& map, Saved& saved, const K& key) {
if (saved.find(key) != saved.end()) {
return;
}
auto it = map.find(key);
saved.emplace(key,
it != map.end()
? std::optional<typename Map::mapped_type>(it->second)
: std::nullopt);
}
// Swaps every recorded entry between `map` and `saved` (self-inverse).
template <typename Map, typename Saved>
static void ToggleMapEntries(Map& map, Saved& saved) {
for (auto& elem : saved) {
const auto& key = elem.first;
auto& other = elem.second;
auto it = map.find(key);
std::optional<typename Map::mapped_type> live =
it != map.end() ? std::optional<typename Map::mapped_type>(it->second)
: std::nullopt;
if (other.has_value()) {
// insert_or_assign avoids requiring a default-constructible mapped_type
// (e.g. MutableBlobFileMetaData has no default constructor).
map.insert_or_assign(key, std::move(*other));
} else if (it != map.end()) {
map.erase(it);
}
other = std::move(live);
}
}
template <typename Set, typename Saved, typename K>
static void RecordSetMembership(const Set& set, Saved& saved, const K& key) {
if (saved.find(key) != saved.end()) {
return;
}
saved.emplace(key, set.find(key) != set.end());
}
template <typename Set, typename Saved>
static void ToggleSetMembership(Set& set, Saved& saved) {
for (auto& elem : saved) {
const auto& key = elem.first;
bool& other_present = elem.second;
bool live_present = set.find(key) != set.end();
if (other_present) {
set.insert(key);
} else {
set.erase(key);
}
other_present = live_present;
}
}
void RecordAddedFileEntry(int level, uint64_t fn) {
assert(undo_);
assert(level < num_levels_);
auto key = std::make_pair(level, fn);
if (undo_->added_files.find(key) != undo_->added_files.end()) {
return;
}
const auto& map = levels_[level].added_files;
auto it = map.find(fn);
undo_->added_files.emplace(
key, it != map.end() ? std::optional<FileMetaData*>(it->second)
: std::nullopt);
}
void RecordDeletedFileEntry(int level, uint64_t fn) {
assert(undo_);
assert(level < num_levels_);
auto key = std::make_pair(level, fn);
if (undo_->deleted_files.find(key) != undo_->deleted_files.end()) {
return;
}
const auto& set = levels_[level].deleted_files;
undo_->deleted_files.emplace(key, set.find(fn) != set.end());
}
// Snapshots the pre-edit value of every piece of state that `edit` will
// mutate, so the edit can later be rolled back / redone. Must be called
// before the edit is applied and only in tracking mode.
void CaptureUndo(const VersionEdit* edit) {
assert(track_found_and_missing_files_);
assert(!undo_);
undo_ = std::make_unique<EditUndo>();
undo_->intermediate_files_size = intermediate_files_.size();
// Snapshot the pre-edit scalars (the MutableScalars base subobject).
up_cast<MutableScalars>(*undo_) = up_cast<MutableScalars>(*this);
auto touch_table_file = [&](int level, uint64_t fn) {
RecordMapEntry(table_file_levels_, undo_->table_file_levels, fn);
RecordSetMembership(found_files_, undo_->found_files, fn);
RecordSetMembership(l0_missing_files_, undo_->l0_missing_files, fn);
RecordSetMembership(non_l0_missing_files_, undo_->non_l0_missing_files,
fn);
if (level >= 0 && level < num_levels_) {
RecordAddedFileEntry(level, fn);
RecordDeletedFileEntry(level, fn);
} else if (level >= num_levels_) {
RecordMapEntry(invalid_level_sizes_, undo_->invalid_level_sizes, level);
}
};
auto touch_blob_file = [&](uint64_t blob_file_number) {
if (blob_file_number == kInvalidBlobFileNumber) {
return;
}
RecordMapEntry(mutable_blob_file_metas_, undo_->blob_metas,
blob_file_number);
RecordSetMembership(missing_blob_files_, undo_->missing_blob_files,
blob_file_number);
};
for (const auto& ba : edit->GetBlobFileAdditions()) {
touch_blob_file(ba.GetBlobFileNumber());
}
for (const auto& bg : edit->GetBlobFileGarbages()) {
touch_blob_file(bg.GetBlobFileNumber());
}
for (const auto& deleted_file : edit->GetDeletedFiles()) {
const int level = deleted_file.first;
const uint64_t fn = deleted_file.second;
touch_table_file(level, fn);
// Only reachable blob metadata is touched (mirrors ApplyFileDeletion).
if (level >= 0 && level < num_levels_ &&
GetCurrentLevelForTableFile(fn) == level) {
touch_blob_file(GetOldestBlobFileNumberForTableFile(level, fn));
}
}
for (const auto& new_file : edit->GetNewFiles()) {
const int level = new_file.first;
const FileMetaData& meta = new_file.second;
touch_table_file(level, meta.fd.GetNumber());
touch_blob_file(meta.oldest_blob_file_number);
}
for (const auto& cursor : edit->GetCompactCursors()) {
const int level = cursor.first;
if (level >= 0 && level < num_levels_) {
RecordMapEntry(updated_compact_cursors_, undo_->compact_cursors, level);
}
}
}
// Swaps the live state with the recorded "other side", moving between the
// post-edit and pre-edit states. Self-inverse.
void ToggleUndo() {
assert(undo_);
for (auto& elem : undo_->added_files) {
const int level = elem.first.first;
const uint64_t fn = elem.first.second;
auto& other = elem.second;
auto& map = levels_[level].added_files;
auto it = map.find(fn);
std::optional<FileMetaData*> live =
it != map.end() ? std::optional<FileMetaData*>(it->second)
: std::nullopt;
if (other.has_value()) {
map[fn] = *other;
} else if (it != map.end()) {
// Note: erasing the map entry does not delete the FileMetaData; its
// lifetime is managed via `deferred_unref` / the built Version's ref.
map.erase(it);
}
other = live;
}
for (auto& elem : undo_->deleted_files) {
const int level = elem.first.first;
const uint64_t fn = elem.first.second;
bool& other_present = elem.second;
auto& set = levels_[level].deleted_files;
bool live_present = set.find(fn) != set.end();
if (other_present) {
set.insert(fn);
} else {
set.erase(fn);
}
other_present = live_present;
}
ToggleMapEntries(table_file_levels_, undo_->table_file_levels);
ToggleMapEntries(invalid_level_sizes_, undo_->invalid_level_sizes);
ToggleMapEntries(updated_compact_cursors_, undo_->compact_cursors);
ToggleMapEntries(mutable_blob_file_metas_, undo_->blob_metas);
ToggleSetMembership(found_files_, undo_->found_files);
ToggleSetMembership(l0_missing_files_, undo_->l0_missing_files);
ToggleSetMembership(non_l0_missing_files_, undo_->non_l0_missing_files);
ToggleSetMembership(missing_blob_files_, undo_->missing_blob_files);
if (!undo_->rolled_back) {
// Forward -> pre-edit: peel off the entries appended by this edit.
for (size_t i = undo_->intermediate_files_size;
i < intermediate_files_.size(); ++i) {
undo_->intermediate_tail.push_back(std::move(intermediate_files_[i]));
}
intermediate_files_.resize(undo_->intermediate_files_size);
} else {
// Pre-edit -> forward: re-append.
for (auto& s : undo_->intermediate_tail) {
intermediate_files_.push_back(std::move(s));
}
undo_->intermediate_tail.clear();
}
// Swap the scalar/flag state (the MutableScalars base subobject).
std::swap(up_cast<MutableScalars>(*this), up_cast<MutableScalars>(*undo_));
undo_->rolled_back = !undo_->rolled_back;
}
// Reverts the most recent Apply() to the pre-edit state.
void RollbackLastApply() {
assert(undo_);
assert(!undo_->rolled_back);
ToggleUndo();
}
// Re-applies the most recent Apply() after a RollbackLastApply().
void RedoLastApply() {
assert(undo_);
assert(undo_->rolled_back);
ToggleUndo();
}
// Finalizes the most recent Apply(): frees FileMetaData whose deletion was
// deferred and drops the undo record. Safe to call with no pending edit.
void CommitLastApply() {
if (!undo_) {
return;
}
if (undo_->rolled_back) {
// Always finalize in the forward (post-edit) state.
ToggleUndo();
}
for (FileMetaData* f : undo_->deferred_unref) {
UnrefFile(f);
}
undo_.reset();
}
// Mapping used for checking the consistency of links between SST files and
// blob files. It is built using the forward links (table file -> blob file),
// and is subsequently compared with the inverse mapping stored in the
@@ -536,7 +837,7 @@ class VersionBuilder::Rep {
if (epoch_number_requirement ==
EpochNumberRequirement::kMightMissing) {
if (!level_zero_cmp_by_seqno_->operator()(lhs, rhs)) {
if (!level_zero_cmp_by_seqno_(lhs, rhs)) {
std::ostringstream oss;
oss << "L0 files are not sorted properly: files #"
<< lhs->fd.GetNumber() << " with seqnos (largest, smallest) "
@@ -568,7 +869,7 @@ class VersionBuilder::Rep {
}
}
if (!level_zero_cmp_by_epochno_->operator()(lhs, rhs)) {
if (!level_zero_cmp_by_epochno_(lhs, rhs)) {
std::ostringstream oss;
oss << "L0 files are not sorted properly: files #"
<< lhs->fd.GetNumber() << " with epoch number "
@@ -597,7 +898,7 @@ class VersionBuilder::Rep {
assert(lhs);
assert(rhs);
if (!level_nonzero_cmp_->operator()(lhs, rhs)) {
if (!level_nonzero_cmp_(lhs, rhs)) {
std::ostringstream oss;
oss << 'L' << level << " files are not sorted properly: files #"
<< lhs->fd.GetNumber() << ", #" << rhs->fd.GetNumber();
@@ -895,7 +1196,12 @@ class VersionBuilder::Rep {
auto& add_files = level_state.added_files;
auto add_it = add_files.find(file_number);
if (add_it != add_files.end()) {
UnrefFile(add_it->second);
if (undo_) {
// Defer freeing so a rollback of this edit can reinstall the file.
undo_->deferred_unref.push_back(add_it->second);
} else {
UnrefFile(add_it->second);
}
add_files.erase(add_it);
}
@@ -976,7 +1282,7 @@ class VersionBuilder::Rep {
delete f;
s = Status::MemoryLimit(
"Can't allocate " +
kCacheEntryRoleToCamelString[static_cast<std::uint32_t>(
kCacheEntryRoleToCamelString[lossless_cast<std::uint32_t>(
CacheEntryRole::kFileMetadata)] +
" due to exceeding the memory limit "
"based on "
@@ -1054,6 +1360,14 @@ class VersionBuilder::Rep {
}
}
// In tracking mode, snapshot how to undo this edit before mutating, so
// VersionEditHandlerPointInTime can build a Version from the pre-edit state
// (on a valid->invalid negative edge) via RollbackLastApply() instead of
// copying the whole Rep on every edit.
if (track_found_and_missing_files_) {
CaptureUndo(edit);
}
// Note: we process the blob file related changes first because the
// table file addition/deletion logic depends on the blob files
// already being there.
@@ -1491,13 +1805,13 @@ class VersionBuilder::Rep {
}
if (epoch_number_requirement == EpochNumberRequirement::kMightMissing) {
SaveSSTFilesTo(vstorage, /* level */ 0, *level_zero_cmp_by_seqno_);
SaveSSTFilesTo(vstorage, /* level */ 0, level_zero_cmp_by_seqno_);
} else {
SaveSSTFilesTo(vstorage, /* level */ 0, *level_zero_cmp_by_epochno_);
SaveSSTFilesTo(vstorage, /* level */ 0, level_zero_cmp_by_epochno_);
}
for (int level = 1; level < num_levels_; ++level) {
SaveSSTFilesTo(vstorage, level, *level_nonzero_cmp_);
SaveSSTFilesTo(vstorage, level, level_nonzero_cmp_);
}
}
@@ -1551,13 +1865,13 @@ class VersionBuilder::Rep {
if (epoch_number_requirement == EpochNumberRequirement::kMightMissing) {
MergeUnorderdAddedFilesWithBase(
base_files, unordered_added_files, *level_zero_cmp_by_seqno_,
base_files, unordered_added_files, level_zero_cmp_by_seqno_,
[&](FileMetaData* file) {
expected_sorted_l0_files.push_back(file);
});
} else {
MergeUnorderdAddedFilesWithBase(
base_files, unordered_added_files, *level_zero_cmp_by_epochno_,
base_files, unordered_added_files, level_zero_cmp_by_epochno_,
[&](FileMetaData* file) {
expected_sorted_l0_files.push_back(file);
});
@@ -1747,11 +2061,11 @@ Status VersionBuilder::LoadTableHandlers(
max_file_size_for_l0_meta_pin, read_options);
}
void VersionBuilder::CreateOrReplaceSavePoint() {
assert(rep_);
savepoint_ = std::move(rep_);
rep_ = std::make_unique<Rep>(*savepoint_);
}
void VersionBuilder::RollbackLastApply() { rep_->RollbackLastApply(); }
void VersionBuilder::RedoLastApply() { rep_->RedoLastApply(); }
void VersionBuilder::CommitLastApply() { rep_->CommitLastApply(); }
bool VersionBuilder::ValidVersionAvailable() {
return rep_->ValidVersionAvailable();
@@ -1765,29 +2079,6 @@ std::vector<std::string>& VersionBuilder::GetAndClearIntermediateFiles() {
void VersionBuilder::ClearFoundFiles() { return rep_->ClearFoundFiles(); }
Status VersionBuilder::SaveSavePointTo(VersionStorageInfo* vstorage) const {
if (!savepoint_ || !savepoint_->ValidVersionAvailable()) {
return Status::InvalidArgument();
}
return savepoint_->SaveTo(vstorage);
}
Status VersionBuilder::LoadSavePointTableHandlers(
InternalStats* internal_stats, int max_threads,
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
const MutableCFOptions& mutable_cf_options,
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options) {
if (!savepoint_ || !savepoint_->ValidVersionAvailable()) {
return Status::InvalidArgument();
}
return savepoint_->LoadTableHandlers(
internal_stats, max_threads, prefetch_index_and_filter_in_cache,
is_initial_load, mutable_cf_options, max_file_size_for_l0_meta_pin,
read_options);
}
void VersionBuilder::ClearSavePoint() { savepoint_.reset(nullptr); }
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
ColumnFamilyData* cfd, VersionEditHandler* version_edit_handler,
bool track_found_and_missing_files, bool allow_incomplete_valid_version)
+12 -24
View File
@@ -64,14 +64,6 @@ class VersionBuilder {
//============APIs only used by VersionEditHandlerPointInTime ============//
// Creates a save point for the Version that has been built so far. Subsequent
// VersionEdits applied to the builder will not affect the Version in this
// save point. VersionBuilder currently only supports creating one save point,
// so when `CreateOrReplaceSavePoint` is called again, the previous save point
// is cleared. `ClearSavePoint` can be called explicitly to clear
// the save point too.
void CreateOrReplaceSavePoint();
// The builder can find all the files to build a `Version`. Or if
// `allow_incomplete_valid_version_` is true and the version history is never
// edited in an atomic group, and only a suffix of L0 SST files and their
@@ -94,27 +86,23 @@ class VersionBuilder {
// Clearing all the found files in this Version.
void ClearFoundFiles();
// Save the Version in the save point to the provided `vstorage`.
// Non-OK status will be returned if there is not a valid save point.
Status SaveSavePointTo(VersionStorageInfo* vstorage) const;
// Load all the table handlers for the Version in the save point.
// Non-OK status will be returned if there is not a valid save point.
Status LoadSavePointTableHandlers(InternalStats* internal_stats,
int max_threads,
bool prefetch_index_and_filter_in_cache,
bool is_initial_load,
const MutableCFOptions& mutable_cf_options,
size_t max_file_size_for_l0_meta_pin,
const ReadOptions& read_options);
void ClearSavePoint();
// Single-edit undo for building a Version reflecting the state *before* the
// most recent `Apply()`. On a valid->invalid negative edge,
// `RollbackLastApply()` reverts the builder to the pre-edit state (so
// `SaveTo`/`LoadTableHandlers` build the pre-edit Version), then
// `RedoLastApply()` restores the post-edit state to continue replay.
// `CommitLastApply()` finalizes the most recent `Apply()` (freeing files
// whose deletion was deferred) and must be called once per `Apply()`; it is a
// no-op if there is nothing pending. This replaces the previous per-edit
// whole-Rep save-point copy, which was quadratic over a MANIFEST.
void RollbackLastApply();
void RedoLastApply();
void CommitLastApply();
//======= End of APIs only used by VersionEditPointInTime==========//
private:
class Rep;
std::unique_ptr<Rep> savepoint_;
std::unique_ptr<Rep> rep_;
};
+123
View File
@@ -3,19 +3,25 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "db/version_builder.h"
#include <cstring>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include "db/db_test_util.h"
#include "db/version_edit.h"
#include "db/version_set.h"
#include "rocksdb/advanced_options.h"
#include "rocksdb/sst_file_writer.h"
#include "table/unique_id_impl.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/defer.h"
#include "util/string_util.h"
#include "utilities/fault_injection_fs.h"
namespace ROCKSDB_NAMESPACE {
@@ -153,6 +159,123 @@ class VersionBuilderTest : public testing::Test {
void UpdateVersionStorageInfo() { UpdateVersionStorageInfo(&vstorage_); }
};
class VersionBuilderDBTest : public DBTestBase {
public:
VersionBuilderDBTest()
: DBTestBase("version_builder_db_test", /*env_do_fsync=*/false) {}
};
// Regression test: when LogAndApply fails after VersionBuilder pinned a
// table reader for a newly-added file (via LoadTableHandlers), the table
// cache entry must be evicted by VersionBuilder::UnrefFile when the
// FileMetaData is destroyed.
//
// This test faithfully reproduces the crash-test failure mode
// ("File N is not live nor quarantined" from TEST_VerifyNoObsoleteFilesCached
// inside Close on ASAN):
//
// 1. IngestExternalFile submits an AddFile edit through LogAndApply.
// 2. LoadTableHandlers inside ProcessManifestWrites pins a table reader
// for the new file, creating a table cache entry.
// 3. SyncManifest fails (fault injected). The error handler adds the
// ingested file to its quarantine list.
// 4. A subsequent successful LogAndApply (via manual Resume() and then
// Flush) clears the quarantine (version_set.cc:
// `ClearFilesToQuarantine`).
// 5. Metadata read fault injection prevents FindObsoleteFiles from
// scanning the directory, so the on-disk orphan is not discovered.
// 6. On DB Close, TEST_VerifyNoObsoleteFilesCached iterates the table
// cache and finds an entry for a file that is neither live nor
// quarantined -> assertion fires (without the fix).
//
// The IngestExternalFile path issues a few no-op manifest writes before
// the actual AddFile edit; we arm the injection only after
// LoadTableHandlers has actually pinned a reader for the ingested file.
TEST_F(VersionBuilderDBTest,
IngestExternalFileEvictsTableCacheOnLogAndApplyFailure) {
auto fault_fs = std::make_shared<FaultInjectionTestFS>(env_->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
Options options = CurrentOptions();
options.env = fault_env.get();
options.disable_auto_compactions = true;
// Disable auto-recovery so recovery runs synchronously via our explicit
// Resume() call -- no background thread to race with test teardown.
options.max_bgerror_resume_count = 0;
options.paranoid_file_checks = true;
DestroyAndReopen(options);
// RAII: ensure the DB is closed before fault_env goes out of scope,
// even if an ASSERT_* below early-returns.
Defer closer([this]() { Close(); });
// Seed the memtable with data so a subsequent Flush actually produces a
// successful LogAndApply (and thus clears the quarantine).
ASSERT_OK(Put("k1", "v1"));
// Build an external SST through the public SstFileWriter API.
const std::string ext_file = dbname_ + "/ingest_me.sst";
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.Open(ext_file));
ASSERT_OK(writer.Put("k2", "v2"));
ExternalSstFileInfo file_info;
ASSERT_OK(writer.Finish(&file_info));
// Arm the injection only once we have observed VersionBuilder actually
// pin a table reader for the ingested file (i.e. after FindTable runs).
std::atomic<bool> injection_armed{false};
std::atomic<bool> injected_once{false};
SyncPoint::GetInstance()->SetCallBack("TableCache::FindTable:0",
[&](void*) { injection_armed = true; });
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::ProcessManifestWrites:AfterSyncManifest", [&](void* arg) {
if (!injection_armed.load()) {
return;
}
if (injected_once.exchange(true)) {
return;
}
auto* ptr = static_cast<IOStatus*>(arg);
assert(ptr);
*ptr = IOStatus::IOError("injected");
// Retryable IOError -> soft error, recoverable via Resume().
ptr->SetRetryable(true);
});
SyncPoint::GetInstance()->EnableProcessing();
IngestExternalFileOptions ifo;
Status s = db_->IngestExternalFile({ext_file}, ifo);
ASSERT_NOK(s);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Manual synchronous recovery: clear the soft bg error, then do a
// successful LogAndApply (via Flush) which triggers
// ClearFilesToQuarantine (version_set.cc line ~6591). This puts the
// ingested file into the "not live nor quarantined" state.
ASSERT_OK(dbfull()->Resume());
ASSERT_OK(Flush());
// Prevent the FindObsoleteFiles full-directory scan from finding the
// orphan on disk. Mirrors the crash test's open_metadata_read_fault_one_in.
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kMetadataRead, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
Defer disable_fault([&] {
fault_fs->DisableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
});
// The RAII Defer above will run Close() on scope exit. Close's
// TEST_VerifyNoObsoleteFilesCached (active on ASAN) iterates the table
// cache. Without the D99759696 fix, an entry for the ingested file
// remains -- a file that is neither live nor quarantined -- and the
// assertion fires. With the fix, VersionBuilder::UnrefFile evicts the
// entry when the discarded FileMetaData is destroyed.
}
void UnrefFilesInVersion(VersionStorageInfo* new_vstorage) {
for (int i = 0; i < new_vstorage->num_levels(); i++) {
for (auto* f : new_vstorage->LevelFiles(i)) {
Loaded 100 of 226 files, more files were not shown because too many files have changed in this diff. Show more