Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15100 Fix the TSAN race in T284274705 where `CoroutineStatsRequestData::onSet()` can access its borrowed `Env*` after `AsyncCallback::OnComplete()` allows the caller to destroy that environment. When `co_await baton` suspends, the Task's implicit `ViaIfAsync` saves the read's statistics `RequestContext` (`S`) as the context needed by the real RocksDB continuation. The old explicit `executor->add()` also ran the Baton bridge under `S`. Consequently, `baton.post()` resumed the Via trampoline under `S`, and the trampoline's own `executor_->add()` caused EventBase to capture `S` a second time as the queued continuation's outer context. After the read removed its stats scope and called `OnComplete()`, the Via guard restored that outer `S`, triggering a late `onSet()` that dereferenced the potentially destroyed `Env*`. Remove the redundant outer executor hop and call `baton.post()` directly under an explicitly null `RequestContext`. If the read is suspended, `post()` resumes only the Via trampoline; the trampoline still queues the real continuation on the bound read executor. The Via promise retains its saved `S`, so `executeContinuation()` installs `S` while RocksDB executes, but EventBase now captures null as the outer context restored afterward. If filesystem completion happens before `co_await baton`, `post()` only marks the Baton ready and the read continues inline under its existing context. Thread Race: `E` = temporary backup `Env` `S` = statistics `RequestContext` containing `CoroutineStatsRequestData{env_ = E}` `P` = parent context without RocksDB statistics `B` = old explicit Baton bridge `C` = `ViaIfAsync` callback that resumes the real read ``` Caller / stress thread RocksDB read EventBase thread ---------------------- ----------------------------- BackupInfo owns shared_ptr<Env> E Call MultiGetAsync() Wait for OnComplete() Start the RocksDB read coroutine Install S: P -> S S.onSet() Call SubmitReadAsync() No io_uring is available, so: Read() executes synchronously filesystem callback runs inline callback is still under S Old code calls executor->add(B) EventBase records: B.outer_context = S Reach co_await baton B has not executed, so Baton is not ready ViaIfAsync: saves execution_context = S gives trampoline V to Baton Suspend the real read Leave S: S -> P S.onUnset() Later, EventBase runs B Install B.outer_context: P -> S S.onSet() B calls baton.post() -> V resumes inline -> V queues callback C -> EventBase records: C.outer_context = S Later, EventBase runs C Install C.outer_context: P -> S S.onSet() Via executeContinuation(): remembers previous context = S installs saved execution context S resumes the real RocksDB read Read finishes Statistics scope removes S: S -> P S.onUnset() Call OnComplete() Wait returns <--------------- OnComplete signals caller ================ PUBLIC READ IS COMPLETE ================ Destroy restored DB C is still unwinding Release BackupInfo Via guard restores previous context: Last shared_ptr<Env> disappears P -> S Begin Env E destruction S.onSet() -> E->GetThreadID() Env::~Env() <--------------- TSAN race ---------------> S.onSet() ``` The same context was captured in two roles: ``` Via execution_context = S required while the real read executes C.outer_context = S accidentally restored after the read completes ``` The fix removes bridge `B` and posts under null. For an outstanding asynchronous read, the Via trampoline therefore queues `C` with `outer_context = null`, while retaining `execution_context = S`. The real read still executes under `S`, but after `OnComplete()` the Via guard restores null instead of reactivating `S`. Reviewed By: anand1976 Differential Revision: D115967513 fbshipit-source-id: 87d744ecdbad3421c4d2a1a7be6423a3f0373573
RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
RocksDB is developed and maintained by Facebook Database Engineering Team. It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com) and Jeff Dean (jeff@google.com)
This code is a library that forms the core building block for a fast key-value server, especially suited for storing data on flash drives. It has a Log-Structured-Merge-Database (LSM) design with flexible tradeoffs between Write-Amplification-Factor (WAF), Read-Amplification-Factor (RAF) and Space-Amplification-Factor (SAF). It has multi-threaded compactions, making it especially suitable for storing multiple terabytes of data in a single database.
Start with example usage here: https://github.com/facebook/rocksdb/tree/main/examples
See the github wiki for more explanation.
The public interface is in include/. Callers should not include or
rely on the details of any other header files in this package. Those
internal APIs may be changed without warning.
Questions and discussions are welcome on the RocksDB Developers Public Facebook group and email list on Google Groups.
License
RocksDB is dual-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). You may select, at your option, one of the above-listed licenses.