1
0
mirror of https://github.com/facebook/rocksdb.git synced 2026-10-23 08:09:02 +03:00
Files
rocksdb/util/async_file_reader.cc
Jay Huh 956c7042da Check async I/O support on the MultiGet thread and fall back to sync (#15337)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15337

## Why

RocksDB crash tests repeatedly fail with `MultiGetEntity error: Not implemented: ReadAsync: failed to init io_uring` when synchronous `MultiGet` requests async I/O on a thread that cannot create an io_uring. The same `NotSupported` status can reach users if capability detection succeeds but an individual asynchronous submission later fails.

## Previous behavior

`Version` cached `FSSupportedOps::kAsyncIO` when it was constructed. Posix io_uring instances are thread-local, so that result described the construction thread rather than the thread later executing MultiGet. A cached positive result could route a different thread into `ReadAsync`, where io_uring creation failed and the error reached the caller. A cached negative result could also unnecessarily disable async I/O on a capable read thread.

## New behavior and implementation

Synchronous `Version::MultiGet` now checks async-I/O support on the thread executing the read. Both the cross-level `optimize_multiget_for_io` path and the within-level overlap path enter their coroutine-based I/O only when that thread supports it. Posix `SupportedOps` eagerly creates and retains that thread-local io_uring, so subsequent submissions reuse the verified ring.

The capability remains advisory: if `ReadAsync` nevertheless returns `NotSupported`, `AsyncFileReader` completes the `FSReadRequest` synchronously, including requests that use filesystem-owned buffers. Poll errors are applied only to requests that were actually submitted asynchronously, so synchronous fallbacks in a mixed batch retain their successful status.

Regression coverage forces io_uring capability failure only on a dedicated read thread and exercises both synchronous MultiGet branches. Separate tests cover all-request fallback and a batch mixing submitted and synchronously fallen-back requests under an injected Poll error. A one-line bug-fix release note records the user-visible correction.

## Safety and compatibility

This changes no API, ABI, or persisted format. The support check is only performed when synchronous `MultiGet` receives `ReadOptions::async_io`; the Posix steady-state cost is a virtual call and thread-local ring lookup per such MultiGet. Direct I/O continues to use the existing synchronous `MultiRead` route before `AsyncFileReader`, and native coroutine APIs are unchanged.

Reviewed By: pdillinger

Differential Revision: D117802488

fbshipit-source-id: 0f2991a523607d894436cd53123e7a1b2fe93167
2026-10-12 19:22:21 -07:00

100 lines
3.3 KiB
C++

// 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).
//
#if USE_COROUTINES
#include "util/async_file_reader.h"
#include "test_util/sync_point.h"
namespace ROCKSDB_NAMESPACE {
bool AsyncFileReader::MultiReadAsyncImpl(ReadAwaiter* awaiter) {
if (tail_) {
tail_->next_ = awaiter;
}
tail_ = awaiter;
if (!head_) {
head_ = awaiter;
}
num_reqs_ += awaiter->num_reqs_;
awaiter->io_handle_.resize(awaiter->num_reqs_);
awaiter->del_fn_.resize(awaiter->num_reqs_);
for (size_t i = 0; i < awaiter->num_reqs_; ++i) {
FSReadRequest& req = awaiter->read_reqs_[i];
IOStatus s = awaiter->file_->ReadAsync(
req, awaiter->opts_,
[](FSReadRequest& completed_req, void* cb_arg) {
FSReadRequest* read_req = static_cast<FSReadRequest*>(cb_arg);
read_req->status = completed_req.status;
read_req->result = completed_req.result;
if (completed_req.fs_scratch != nullptr) {
read_req->fs_scratch = std::move(completed_req.fs_scratch);
}
},
&req, &awaiter->io_handle_[i], &awaiter->del_fn_[i],
/*aligned_buf=*/nullptr, awaiter->dbg_);
if (s.IsNotSupported()) {
// SupportedOps is advisory, so ReadAsync can still report NotSupported
// when the request is submitted. Read synchronously instead so the
// MultiGet caller doesn't see a spurious error. FilePrefetchBuffer and
// IODispatcher use the same fallback.
TEST_SYNC_POINT("AsyncFileReader::MultiReadAsyncImpl:SyncFallback");
awaiter->io_handle_[i] = nullptr;
awaiter->del_fn_[i] = nullptr;
awaiter->file_->Read(awaiter->opts_, &req,
/*direct_io_buffer_context=*/nullptr, awaiter->dbg_);
} else if (!s.ok()) {
// For any non-ok status, the FileSystem will not call the callback
// So let's update the status ourselves
req.status = s;
}
}
return true;
}
void AsyncFileReader::Wait() {
if (!head_) {
return;
}
ReadAwaiter* waiter;
std::vector<void*> io_handles;
IOStatus s;
io_handles.reserve(num_reqs_);
waiter = head_;
do {
for (size_t i = 0; i < waiter->num_reqs_; ++i) {
if (waiter->io_handle_[i]) {
io_handles.push_back(waiter->io_handle_[i]);
}
}
} while (waiter != tail_ && (waiter = waiter->next_));
if (io_handles.size() > 0) {
StopWatch sw(SystemClock::Default().get(), stats_, POLL_WAIT_MICROS);
s = fs_->Poll(io_handles, io_handles.size());
}
TEST_SYNC_POINT_CALLBACK("AsyncFileReader::Wait:InjectPollStatus", &s);
do {
waiter = head_;
head_ = waiter->next_;
for (size_t i = 0; i < waiter->num_reqs_; ++i) {
if (waiter->io_handle_[i] && waiter->del_fn_[i]) {
waiter->del_fn_[i](waiter->io_handle_[i]);
}
if (waiter->io_handle_[i] && waiter->read_reqs_[i].status.ok() &&
!s.ok()) {
// Override the request status with the Poll error
waiter->read_reqs_[i].status = s;
}
}
waiter->awaiting_coro_.resume();
} while (waiter != tail_);
head_ = tail_ = nullptr;
RecordInHistogram(stats_, MULTIGET_IO_BATCH_SIZE, num_reqs_);
num_reqs_ = 0;
}
} // namespace ROCKSDB_NAMESPACE
#endif // USE_COROUTINES