Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15436 ## Why Long-held global mutexes can stall foreground and background work. Existing mutex statistics measure acquisition waits, not continuous ownership. This adds opt-in logging to identify the mutex, owning thread, operation and release site. ## Enable for an investigation Provide an INFO logger and shared `Statistics` object when opening the DB, then: - Set the shared statistics level to `StatsLevel::kAll`. - Set `mutex_hold_log_threshold_us` through `DB::SetDBOptions`; `100000` means 100 ms. - Restore threshold 0 and the previous statistics level afterward. The threshold defaults to **0 (disabled)**. `kAll` alone does not enable these hold probes; threshold 0 leaves existing statistics timers unchanged. ## Output Illustrative records, not production observations: ``` SLOW_GLOBAL_MUTEX mutex=DBImpl::mutex_ mutex_addr=0x12340000 owner_thread_id=12345 thread_operation=Compaction hold_us=200000 threshold_us=100000 observed_end_us=1792205400000000 buffer_dropped_records=0 unlock=db_impl_compaction_flush.cc:4660 BackgroundCallCompaction() SLOW_GLOBAL_MUTEX mutex=DBImpl::wal_write_mutex_ mutex_addr=0x12340100 owner_thread_id=12346 thread_operation= hold_us=35000 threshold_us=25000 observed_end_us=1792205401000000 buffer_dropped_records=0 scope_exit=db_impl_write.cc:2250 PreprocessWrite() ``` The first proves a completed 200 ms DB-mutex ownership interval on a thread tagged `Compaction`. The second uses a 25 ms threshold; an empty operation tag means none was available. Neither record reports acquisition wait or proves the cause of a stall. Release attribution uses compiler-embedded strings available in optimized, stripped builds, with a GCC/Clang fallback where standard source locations are unavailable. Direct unlock and condition wait identify their call sites; scope exit and temporary unlock identify guard declarations, not closing braces. No stack unwinding or symbol lookup. ## Scope and safety - Track six selected mutexes: DB, WAL, point-lock wait graph, two prepare-log bookkeeping mutexes and transaction name map. This is not comprehensive synchronization tracing. - Acquisition waits and condition-variable release gaps are excluded from hold duration. Configuration is pre-first-use; repeated setup retains the first valid context. - Sample at most once per mutex per second, with eight deferred records per thread. `buffer_dropped_records` counts capacity overflow only; rate limiting, pause and emission contention can omit samples. - Emit after covered outer lock scopes release. Emission is nonblocking best-effort; shutdown drains/pauses emission and stops diagnostics before the owned logger closes. Closing from that logger's own callback is unsupported. - No data-file format change. The mutable option is persisted in OPTIONS and wired into bench/stress tools. Typed C binding is deferred; string DB-option APIs are available. Disabled bookkeeping still has a cost. Release A/B measurements found about 3 ns added per selected mutex pair at level 1, and about 70 ns for hold timing over `kAll`/0. Get and reused-iterator Seek showed no resolved small probe effect; fresh iterator+Seek at 16 threads showed +8.7% CPU/query and -9.4% QPS. No logs were emitted in those benchmarks; logging cost and broader workload qualification remain open. Reviewed By: xingbowang Differential Revision: D125790955 fbshipit-source-id: 1096163f3bd25ffee585dc6ec8ebfd7f1759851a
RocksDB C API Code Generation Prototype
This directory contains a prototype for semi-automating parts of
RocksDB's C API based on the existing C++ public API and the current C API
conventions in include/rocksdb/c.h / db/c.cc.
The intent is not to automatically wrap arbitrary C++ APIs. Instead, the goal is to reduce maintenance burden for the parts of the C API that are already highly mechanical today.
Why a semi-automated approach
RocksDB's C API follows stable conventions that downstream users rely on:
- opaque handle types like
rocksdb_t*,rocksdb_transaction_t* Statustranslated tochar** errptrSlicetranslated toconst char*+size_t- binary/string results copied into
malloc()-owned buffers - booleans represented as
unsigned char rocksdb_<object>_<verb>[_cf][_with_ts]naming
These conventions are valuable and should remain the source of truth for the generated code.
At the same time, the C++ public API includes features that are poor fits for fully automatic wrapper generation:
- callback-backed polymorphic types (
Comparator,MergeOperator, etc.) - complex object construction/open flows
- ownership-transferring factory setters
- vector-heavy marshalling APIs like the
MultiGetfamily - virtual extension points and custom bridging logic
For those cases, hand-designed wrappers remain the right tool.
Why the spec-driven generator still exists
The spec-driven generator is still needed for wrappers whose C shape cannot be reliably inferred from the C++ declaration alone.
Yes, this means spec.json is manually maintained. That is intentional. The
goal is not to eliminate human decisions; the goal is to stop hand-writing the
same declaration/definition boilerplate once those decisions have been made.
For many wrappers, someone still has to decide:
- the C function name and parameter order
- whether a C++
Statusbecomeschar** errptr - whether default C++ objects should be synthesized, such as
ReadOptions() - how ownership and lifetime should work for pointers, factories, and callbacks
- how vectors, maps, and option collections should be flattened into C
- whether the C API should intentionally differ from the C++ API shape
Those are API design choices, not mechanical translations. The spec-driven path keeps those choices explicit while still generating consistent boilerplate.
Backward compatibility with the existing hand-written C API is one input, but it is not the primary reason this layer exists. Even for brand-new wrappers, we still need an explicit policy layer for non-trivial C bindings.
What this prototype focuses on
The generator currently targets representative examples from the easiest and most repetitive wrapper families:
-
Simple scalar field setters/getters
rocksdb_options_set_create_if_missingrocksdb_options_get_create_if_missingrocksdb_readoptions_set_fill_cacherocksdb_writeoptions_set_syncrocksdb_block_based_options_set_block_sizerocksdb_cuckoo_options_set_hash_ratio
-
Simple forwarding wrappers with no
Statusrocksdb_writebatch_clearrocksdb_writebatch_putrocksdb_writebatch_put_cfrocksdb_writebatch_deleterocksdb_writebatch_put_log_datarocksdb_writebatch_set_save_pointrocksdb_transaction_set_savepoint
-
Simple forwarding wrappers with
Status->char** errptrrocksdb_put,rocksdb_put_cfrocksdb_delete,rocksdb_delete_cfrocksdb_merge,rocksdb_merge_cfrocksdb_writerocksdb_writebatch_rollback_to_save_pointrocksdb_writebatch_pop_save_pointrocksdb_transaction_put,rocksdb_transaction_put_cfrocksdb_transaction_merge,rocksdb_transaction_merge_cfrocksdb_transaction_delete,rocksdb_transaction_delete_cfrocksdb_transaction_commitrocksdb_transaction_rollbackrocksdb_transaction_rollback_to_savepoint
-
Simple metadata accessors
rocksdb_flushjobinfo_cf_namerocksdb_flushjobinfo_largest_seqno
This is intentionally a constrained subset. The point is to demonstrate a maintainable pattern and a spec format that can be extended gradually.
What should remain manual
These families should stay hand-written unless a future design adds a much richer generator:
- comparators / merge operators / compaction filters / event listeners
- compaction service wrappers
- openers and constructors that build vectors/maps of descriptors/options
MultiGet/ batchedMultiGetfamilies- wrappers involving
std::shared_ptr/std::unique_ptrownership transfer - C APIs whose behavior intentionally differs from the C++ API shape
Design principles
1. Keep the current C API conventions
The code generator should not invent a new style. It should emit code that looks like today's hand-written wrappers:
SaveError(errptr, ...)Slice(key, keylen)- direct field assignments for plain option setters
- same function names and signatures
2. Let auto-discovery handle the simple direct cases
For a small set of mechanically mappable public structs, the generator can discover missing bindings directly from the C++ headers and emit the missing C wrappers automatically. Existing handwritten wrappers still win when they already exist.
Today this strict auto-discovery path is used for:
- simple
ReadOptionsscalar / enum fields - simple scalar / enum fields across selected public option structs
- simple metadata fields on listener/job-info structs
- simple metadata view structs such as table-properties and compaction metadata
For those managed families, unsupported or intentionally deferred fields must
be explicitly recorded in auto_simple_bindings_blocklist.json rather than
being skipped silently. Anything outside those families remains hand-written.
3. Keep explicit spec for non-trivial wrappers
The spec-driven generator remains the right tool for method-style wrappers and other APIs whose C surface requires an explicit design decision. In practice:
- auto-discovery owns simple direct field coverage for selected struct families
spec.jsonowns wrappers that are still manual in design but repetitive in implementation- fully irregular ownership-heavy or callback-heavy APIs remain hand-written
4. Let C++ headers guide validation, not policy
Longer term, Clang/libTooling can help validate that a wrapper spec still matches the underlying C++ method signature. But the C naming, ownership, and error-handling policy should remain explicit in the spec.
Files
Source templates (hand-written; NOT user-includable and NOT compiled directly):
c_base.h- hand-written template for
include/rocksdb/c.h; the generated.h.incfragments are inlined into it to produce the self-contained public header
- hand-written template for
c_base.cc- hand-written template for
db/c.cc; the generated.cc.incfragments are inlined into it to produce the compiled implementation
- hand-written template for
Generators and inputs:
spec.json- declarative input for the spec-driven generator
generate_c_api.py- emits header/source fragments from the spec
auto_simple_bindings.py- auto-discovers simple public field bindings and generates checked-in
.incfiles for them
- auto-discovers simple public field bindings and generates checked-in
auto_simple_bindings_blocklist.json- checked-in exceptions for auto-managed fields that are intentionally manual or temporarily deferred
abi_type_overrides.json- per-function C type pins so already-shipped wrappers keep their historical C signature even when the C++ field type maps to a different C type
regen_all.py- orchestrates both generators and inlines the fragments into
c.h/c.cc
- orchestrates both generators and inlines the fragments into
Verification (run by make check-c-api-gen and in CI):
check_api_completeness.py- asserts every declared public C function has exactly one definition (dependency-free; guards against link-time breakage of bindings)
check_api_compatibility.py+api_compatibility_allowlist.json- signature-level backward-compatibility gate against a reference revision; the authoritative ABI/API-stability check
verify_generated_up_to_date.py- regenerates in a temp dir and confirms the checked-in output is current
validate_generated_equivalence.py+equivalence_allowlist.json- best-effort migration aid: compares generated wrapper bodies against a
PRE-migration hand-written revision. Body comparison is heuristic; for
backward-compatibility rely on
check_api_compatibility.py.
- best-effort migration aid: compares generated wrapper bodies against a
PRE-migration hand-written revision. Body comparison is heuristic; for
backward-compatibility rely on
Toolchain requirements
Regeneration needs two external tools:
clang++(libclang) — used to dump the C++ struct ASTs that drive auto-discovery. Resolution order is$CXX(if it names a clang), then bareclang++, then versioned fallbacks (clang++-21…clang++-13).clang-format— used to canonicalize the generated.incfragments (and therefore the inlinedc.h/c.cc).
The checked-in generated output is byte-reproducible only for a pinned
clang-format version. CI uses clang-format-21 (see
.github/workflows/pr-jobs.yml); pin the same version locally so your
regeneration matches CI:
python3 tools/c_api_gen/regen_all.py --clang-format clang-format-21
# or, when running the staleness check via make:
make check-c-api-gen CLANG_FORMAT_BINARY=clang-format-21
If clang++ is not installed, make check skips the C API staleness check
with a message instead of failing, so it still works without the codegen
toolchain. CI is the authoritative gate.
The dedicated build-linux-clang-21-no_test_run CI job runs this target with
CHECK_C_API_GEN_STRICT=1, which turns every such "skip" (missing clang++ or
clang-format, or an unresolvable compat baseline ref) into a hard error, so the
core CI coverage cannot silently regress to a no-op:
make check-c-api-gen CHECK_C_API_GEN_STRICT=1 CLANG_FORMAT_BINARY=clang-format-21
Running the generators
From the repo root, to regenerate all checked-in fragments and the inlined
c.h / c.cc:
python3 tools/c_api_gen/regen_all.py
This regenerates:
- the spec-driven fragments from
spec.json - the auto-discovered fragments produced by
auto_simple_bindings.py - the round-trip tests produced by
gen_roundtrip_tests.py
To preview only the spec-driven output for one section without touching the
checked-in fragments, run generate_c_api.py directly with --header-out /
--source-out pointing at a scratch path (e.g. under /tmp).
To verify the auto-discovered checked-in output is up to date:
python3 tools/c_api_gen/auto_simple_bindings.py --check
To verify the full checked-in generated set is stable without relying on Git worktree metadata:
python3 tools/c_api_gen/verify_generated_up_to_date.py
Maintaining Auto-Managed Families
When a new public field is added to an auto-managed family, use this decision process:
- If it is a simple scalar, enum, string, or supported chrono field and the
desired C API shape matches the existing conventions, do not hand-edit
include/rocksdb/c.hordb/c.cc. Runpython3 tools/c_api_gen/regen_all.pyand letauto_simple_bindings.pygenerate the wrapper. - If the field needs an explicit C API design because of ownership, callbacks,
vectors, maps, nested structs, or intentionally different C naming/behavior,
keep it out of auto-generation by adding an entry to
tools/c_api_gen/auto_simple_bindings_blocklist.jsonwith"policy": "manual". - If the field belongs to an auto-managed family but you intentionally want to
postpone C API coverage for a short time, add a blocklist entry with
"policy": "deferred"and a concrete reason. Includetracking_issuewhen you have one. - If none of the above applies, the build should fail. Extend
auto_simple_bindings.py, add a spec-driven wrapper, or add a hand-written wrapper and then update the blocklist policy accordingly.
The blocklist is intentionally strict:
- each entry must identify the managed family and field
- each entry must have a non-empty reason
- policy must be either
manualordeferred - stale entries fail regeneration if the field disappears or is renamed
Example deferred entry:
{
"name": "ReadOptions",
"entries": [
{
"field": "future_slice_option",
"policy": "deferred",
"reason": "Needs an explicit C buffer ownership design before exposing it.",
"tracking_issue": "T123456789"
}
]
}
The blocklist only applies to the auto-managed families handled by
auto_simple_bindings.py. It is not a substitute for spec.json, which still
owns non-trivial method-style wrappers. It is also unrelated to
equivalence_allowlist.json, which only suppresses intentional diff mismatches
in the equivalence checker.
To refresh the full checked-in generated set and confirm nothing changes:
python3 tools/c_api_gen/verify_generated_up_to_date.py
To verify active generated wrappers are equivalent to the handwritten wrappers from a reference revision:
python3 tools/c_api_gen/validate_generated_equivalence.py --ref HEAD
This checks the generated fragments currently included by
include/rocksdb/c.h and db/c.cc against HEAD:include/rocksdb/c.h and
HEAD:db/c.cc. Known historical inconsistencies can be documented in
equivalence_allowlist.json with a reason instead of being silently ignored.
Suggested migration path
- Start with generated preview fragments only.
- Migrate a few low-risk existing wrappers to generated sections.
- Keep manual and generated sections clearly separated.
- Let auto-discovery pick up new simple public fields automatically.
- Extend the spec or keep manual wrappers for complex/manual families.
This incremental approach minimizes downstream impact while still reducing maintenance burden over time.