diff --git a/env/env_test.cc b/env/env_test.cc index 4c0939ecffa..fd76a33f70d 100644 --- a/env/env_test.cc +++ b/env/env_test.cc @@ -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 fs = env_->GetFileSystem(); + std::string fname = test::PerThreadDBPath(env_, "testfile_queuefull"); + + constexpr size_t kSectorSize = 4096; + + // 1. Create a test file. + { + std::unique_ptr 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 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(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 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. diff --git a/env/io_posix.cc b/env/io_posix.cc index d28d0da99b7..5fd98fc39fc 100644 --- a/env/io_posix.cc +++ b/env/io_posix.cc @@ -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);