Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions env/env_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3728,6 +3728,71 @@ TEST_F(TestAsyncRead, InterleavingIOUringOperations) {
#endif
}

// Test that ReadAsync returns IOStatus::Busy when io_uring_get_sqe returns
// null (submission queue full), rather than crashing on a null SQE dereference.
// Uses SyncPoint injection to simulate the null SQE since io_uring_submit is
// called per-request, making it difficult to naturally saturate the SQ.
TEST_F(TestAsyncRead, ReadAsyncQueueFull) {
#if defined(ROCKSDB_IOURING_PRESENT)
std::shared_ptr<FileSystem> fs = env_->GetFileSystem();
std::string fname = test::PerThreadDBPath(env_, "testfile_queuefull");

constexpr size_t kSectorSize = 4096;

// 1. Create a test file.
{
std::unique_ptr<FSWritableFile> wfile;
ASSERT_OK(
fs->NewWritableFile(fname, FileOptions(), &wfile, nullptr /*dbg*/));
auto data = NewAligned(kSectorSize * 8, 'x');
Slice slice(data.get(), kSectorSize);
ASSERT_OK(wfile->Append(slice, IOOptions(), nullptr));
ASSERT_OK(wfile->Close(IOOptions(), nullptr));
}

// 2. Open the file and verify ReadAsync handles null SQE gracefully.
{
std::unique_ptr<FSRandomAccessFile> file;
ASSERT_OK(fs->NewRandomAccessFile(fname, FileOptions(), &file, nullptr));

// Force io_uring_get_sqe to appear to return null via SyncPoint.
SyncPoint::GetInstance()->SetCallBack(
"PosixRandomAccessFile::ReadAsync:io_uring_get_sqe",
[](void* arg) { *static_cast<io_uring_sqe**>(arg) = nullptr; });
SyncPoint::GetInstance()->EnableProcessing();

IOOptions opts;
auto scratch = NewAligned(kSectorSize, 0);
FSReadRequest req;
req.offset = 0;
req.len = kSectorSize;
req.scratch = scratch.get();

void* io_handle = nullptr;
IOHandleDeleter del_fn = nullptr;
std::function<void(FSReadRequest&, void*)> callback =
[](FSReadRequest& /*req*/, void* /*cb_arg*/) {};

IOStatus s = file->ReadAsync(req, opts, callback, nullptr, &io_handle,
&del_fn, nullptr);

if (s.IsNotSupported()) {
fprintf(stderr, "Skipping test - io_uring not supported: %s\n",
s.ToString().c_str());
} else {
ASSERT_TRUE(s.IsBusy()) << s.ToString();
ASSERT_EQ(io_handle, nullptr);
ASSERT_EQ(del_fn, nullptr);
}

SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
#else
fprintf(stderr, "Skipping test - ROCKSDB_IOURING_PRESENT not defined\n");
#endif
}

// Helper function to run AbortIO test with parameterized read requests.
// Each request is specified as {offset, length}.
// use_direct_io: if true, opens the file with O_DIRECT to bypass page cache.
Expand Down
11 changes: 11 additions & 0 deletions env/io_posix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,17 @@ IOStatus PosixRandomAccessFile::ReadAsync(
// Step 3: io_uring_sqe_set_data
struct io_uring_sqe* sqe;
sqe = io_uring_get_sqe(iu);
TEST_SYNC_POINT_CALLBACK("PosixRandomAccessFile::ReadAsync:io_uring_get_sqe",
&sqe);
if (sqe == nullptr) {
// Submission queue is full — outstanding completions have not been reaped.
// Clean up the handle we already assigned to the caller and return error.
delete posix_handle;
*io_handle = nullptr;
*del_fn = nullptr;
return IOStatus::Busy(
"PosixRandomAccessFile::ReadAsync: io_uring submission queue is full");
}

io_uring_prep_readv(sqe, fd_, /*sqe->addr=*/&posix_handle->iov,
/*sqe->len=*/1, /*sqe->offset=*/posix_handle->offset);
Expand Down
Loading