Thread safety and free-threading¶
rocksdb-ng supports the free-threaded (“nogil”) builds of CPython. The
extension declares itself free-threading compatible, so importing it on a
free-threaded interpreter does not re-enable the GIL.
This page describes what may and may not be shared between threads.
The closing contract¶
The data path is intentionally lock-free for parallelism. The contract is:
Do not close a DB while other threads are still operating on it.
close() invalidates the underlying RocksDB handle; a get/put that is
in flight on another thread at that moment is a use-after-free, exactly as it
would be a logic error under the GIL. Make sure all worker threads have stopped
using a DB before you close it. (Closing concurrently with other close()
calls, or with a GC-driven finalization, is safe — that is handled internally.)
Objects confined to one thread¶
The following are not safe to share across threads. Confine each to the thread that created it:
Iterators (the objects returned by
iterkeys/itervalues/iteritemsand theirreversedviews). Arocksdb::Iteratoris a single cursor with mutable position; sharing one races the cursor, just as Python’s own iterators are not thread-safe.WriteBatch. A batch is a single-writer builder; build it on one thread, then hand the finished batch to
DB.write.
Options objects¶
An Options, ColumnFamilyOptions, or
TransactionDBOptions object is claimed when it is attached to a
DB or column family and cannot be reused for another until the owner is closed.
Attempting to use one mutable options object for two DBs raises
InvalidArgument (or a ValueError for column families) — and this check is
atomic, so two threads racing to open DBs with the same options object will see
exactly one winner.
Custom comparators and merge operators¶
User-supplied Comparator,
MergeOperator,
AssociativeMergeOperator, and
SliceTransform implementations must be internally
thread-safe. RocksDB invokes these callbacks concurrently from multiple
background threads (for example, parallel subcompactions), and the binding does
not serialize them — doing so would throw away the parallelism free threading
exists to provide. Keep them stateless, or guard any shared state yourself.
Using a free-threaded build¶
Install a free-threaded interpreter (e.g. python3.14t) and run with the GIL
disabled:
PYTHON_GIL=0 python3.14t -c "import rocksdb, sys; print(sys._is_gil_enabled())"
That prints False: importing rocksdb leaves the GIL disabled. The
project’s CI exercises this on the 3.14t build and additionally runs the
concurrency suite under ThreadSanitizer (a ThreadSanitizer-instrumented
interpreter and RocksDB) to catch data races.