summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmake/FindLiburing.cmake10
-rw-r--r--configure.cmake1
-rw-r--r--src/corelib/CMakeLists.txt14
-rw-r--r--src/corelib/configure.cmake22
-rw-r--r--src/corelib/io/qiooperation_p_p.h4
-rw-r--r--src/corelib/io/qioring.cpp79
-rw-r--r--src/corelib/io/qioring_linux.cpp743
-rw-r--r--src/corelib/io/qioring_p.h484
-rw-r--r--src/corelib/io/qrandomaccessasyncfile_p_p.h14
-rw-r--r--src/corelib/io/qrandomaccessasyncfile_qioring.cpp438
-rw-r--r--src/corelib/tools/qlist.h23
-rw-r--r--src/gui/math3d/qmatrix4x4.cpp2
-rw-r--r--src/plugins/platforms/wayland/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/wayland/qwaylanddisplay.cpp2
-rw-r--r--src/plugins/platforms/wayland/qwaylandeventdispatcher.cpp55
-rw-r--r--src/plugins/platforms/wayland/qwaylandeventdispatcher_p.h68
-rw-r--r--src/plugins/platforms/wayland/qwaylandintegration.cpp4
-rw-r--r--src/plugins/sqldrivers/mysql/qsql_mysql.cpp2
-rw-r--r--src/sql/doc/src/sql-driver.qdoc2
-rw-r--r--tests/auto/corelib/io/CMakeLists.txt3
-rw-r--r--tests/auto/corelib/io/qioring/CMakeLists.txt19
-rw-r--r--tests/auto/corelib/io/qioring/data/input.txt1
-rw-r--r--tests/auto/corelib/io/qioring/tst_qioring.cpp263
-rw-r--r--tests/auto/corelib/io/qrandomaccessasyncfile/tst_qrandomaccessasyncfile.cpp22
-rw-r--r--tests/auto/corelib/tools/qlist/tst_qlist.cpp33
25 files changed, 2167 insertions, 142 deletions
diff --git a/cmake/FindLiburing.cmake b/cmake/FindLiburing.cmake
new file mode 100644
index 00000000000..593b525915f
--- /dev/null
+++ b/cmake/FindLiburing.cmake
@@ -0,0 +1,10 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+find_package(PkgConfig QUIET)
+
+pkg_check_modules(Liburing IMPORTED_TARGET "liburing")
+
+if (NOT TARGET PkgConfig::Liburing)
+ set(Liburing_FOUND 0)
+endif()
diff --git a/configure.cmake b/configure.cmake
index 2c01aafa53b..e89922f76f8 100644
--- a/configure.cmake
+++ b/configure.cmake
@@ -1453,6 +1453,7 @@ endif()
qt_configure_add_summary_entry(ARGS "Using vcpkg" TYPE "message" MESSAGE "${_vcpkg_entry_message}")
qt_configure_add_summary_entry(ARGS "libudev")
+qt_configure_add_summary_entry(ARGS "liburing")
qt_configure_add_summary_entry(ARGS "openssl")
qt_configure_add_summary_entry(ARGS "openssl-linked")
qt_configure_add_summary_entry(ARGS "opensslv11")
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 32b70a1f288..e12d824cebb 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -583,6 +583,13 @@ if(QT_FEATURE_async_io)
SOURCES
io/qrandomaccessasyncfile_darwin.mm
)
+ elseif(LINUX AND QT_FEATURE_liburing)
+ qt_internal_extend_target(Core
+ SOURCES
+ io/qrandomaccessasyncfile_qioring.cpp
+ DEFINES
+ QT_RANDOMACCESSASYNCFILE_QIORING
+ )
elseif(QT_FEATURE_thread AND QT_FEATURE_future)
# TODO: This should become the last (fallback) condition later.
# We migth also want to rewrite it so that it does not depend on
@@ -749,6 +756,13 @@ qt_internal_extend_target(Core CONDITION INTEGRITY
--pending_instantiations=128
)
+qt_internal_extend_target(Core CONDITION QT_FEATURE_liburing
+ SOURCES
+ io/qioring.cpp io/qioring_linux.cpp io/qioring_p.h
+ LIBRARIES
+ uring
+)
+
# Workaround for QTBUG-101411
# Remove if QCC (gcc version 8.3.0) for QNX 7.1.0 is no longer supported
qt_internal_extend_target(Core CONDITION QCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "8.3.0")
diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake
index 08908082991..c1d15c75054 100644
--- a/src/corelib/configure.cmake
+++ b/src/corelib/configure.cmake
@@ -43,6 +43,8 @@ qt_find_package(JeMalloc MODULE
PROVIDED_TARGETS PkgConfig::JeMalloc MODULE_NAME core QMAKE_LIB jemalloc)
qt_find_package(Libsystemd MODULE
PROVIDED_TARGETS PkgConfig::Libsystemd MODULE_NAME core QMAKE_LIB journald)
+qt_find_package(Liburing MODULE
+ PROVIDED_TARGETS PkgConfig::Liburing MODULE_NAME global QMAKE_LIB liburing)
qt_find_package(WrapAtomic MODULE
PROVIDED_TARGETS WrapAtomic::WrapAtomic MODULE_NAME core QMAKE_LIB libatomic)
qt_find_package(Libb2 MODULE PROVIDED_TARGETS Libb2::Libb2 MODULE_NAME core QMAKE_LIB libb2)
@@ -402,6 +404,20 @@ int main(void)
}
")
+# liburing
+qt_config_compile_test(liburing
+ LABEL "liburing"
+ LIBRARIES uring
+ CODE
+"#include <liburing.h>
+
+int main(void)
+{
+ io_uring_enter(0, 0, 0, 0, nullptr);
+ return 0;
+}
+")
+
# linkat
qt_config_compile_test(linkat
LABEL "linkat()"
@@ -809,6 +825,11 @@ qt_feature("linkat" PRIVATE
AUTODETECT ( LINUX AND NOT ANDROID ) OR HURD
CONDITION TEST_linkat
)
+qt_feature("liburing" PRIVATE
+ LABEL "liburing"
+ AUTODETECT LINUX
+ CONDITION Liburing_FOUND
+)
qt_feature("std-atomic64" PUBLIC
LABEL "64 bit atomic operations"
CONDITION WrapAtomic_FOUND
@@ -1250,6 +1271,7 @@ qt_configure_add_summary_entry(ARGS "forkfd_pidfd" CONDITION LINUX)
qt_configure_add_summary_entry(ARGS "glib")
qt_configure_add_summary_entry(ARGS "icu")
qt_configure_add_summary_entry(ARGS "jemalloc")
+qt_configure_add_summary_entry(ARGS "liburing")
qt_configure_add_summary_entry(ARGS "timezone_tzdb")
qt_configure_add_summary_entry(ARGS "system-libb2")
qt_configure_add_summary_entry(ARGS "mimetype-database")
diff --git a/src/corelib/io/qiooperation_p_p.h b/src/corelib/io/qiooperation_p_p.h
index 470e0858fd3..be780d4c785 100644
--- a/src/corelib/io/qiooperation_p_p.h
+++ b/src/corelib/io/qiooperation_p_p.h
@@ -24,6 +24,10 @@
#include <QtCore/qspan.h>
#include <QtCore/qvarlengtharray.h>
+#ifdef QT_RANDOMACCESSASYNCFILE_QIORING
+#include <QtCore/private/qioring_p.h>
+#endif
+
#include <variant>
QT_BEGIN_NAMESPACE
diff --git a/src/corelib/io/qioring.cpp b/src/corelib/io/qioring.cpp
new file mode 100644
index 00000000000..28849b49b04
--- /dev/null
+++ b/src/corelib/io/qioring.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#include "qioring_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQIORing, "qt.core.ioring", QtCriticalMsg)
+
+auto QIORing::queueRequestInternal(GenericRequestType &request) -> QueuedRequestStatus
+{
+ if (!ensureInitialized() || preparingRequests) { // preparingRequests protects against recursing
+ // inside callbacks of synchronous completions.
+ finishRequestWithError(request, QFileDevice::ResourceError);
+ addrItMap.remove(&request);
+ return QueuedRequestStatus::CompletedImmediately;
+ }
+ if (!lastUnqueuedIterator)
+ lastUnqueuedIterator.emplace(addrItMap[&request]);
+
+ qCDebug(lcQIORing) << "Trying to submit request" << request.operation()
+ << "user data:" << std::addressof(request);
+ prepareRequests();
+ // If this is now true we have, in some way, fulfilled the request:
+ const bool requestCompleted = !addrItMap.contains(&request);
+ const QueuedRequestStatus requestQueuedState = requestCompleted
+ ? QueuedRequestStatus::CompletedImmediately
+ : QueuedRequestStatus::Pending;
+ // We want to avoid notifying the kernel too often of tasks, so only do it if the queue is full,
+ // otherwise do it when we return to the event loop.
+ if (unstagedRequests == sqEntries && inFlightRequests <= cqEntries) {
+ submitRequests();
+ return requestQueuedState;
+ }
+ if (stagePending || unstagedRequests == 0)
+ return requestQueuedState;
+ stagePending = true;
+ // We are not a QObject, but we always have the notifier, so use that for context:
+ QMetaObject::invokeMethod(
+ std::addressof(*notifier), [this] { submitRequests(); }, Qt::QueuedConnection);
+ return requestQueuedState;
+}
+
+bool QIORing::waitForRequest(RequestHandle handle, QDeadlineTimer deadline)
+{
+ if (!handle || !addrItMap.contains(handle))
+ return true; // : It was never there to begin with (so it is finished)
+ if (unstagedRequests)
+ submitRequests();
+ completionReady(); // Try to process some pending completions
+ while (!deadline.hasExpired() && addrItMap.contains(handle)) {
+ if (!waitForCompletions(deadline))
+ return false;
+ completionReady();
+ }
+ return !addrItMap.contains(handle);
+}
+
+namespace QtPrivate {
+template <typename T>
+using DetectResult = decltype(std::declval<const T &>().result);
+
+template <typename T>
+constexpr bool HasResultMember = qxp::is_detected_v<DetectResult, T>;
+}
+
+void QIORing::finishRequestWithError(QIORing::GenericRequestType &req, QFileDevice::FileError error)
+{
+ invokeOnOp(req, [error](auto *req) {
+ if constexpr (QtPrivate::HasResultMember<decltype(*req)>)
+ req->result.template emplace<QFileDevice::FileError>(error);
+ invokeCallback(*req);
+ });
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qioring_p.cpp"
diff --git a/src/corelib/io/qioring_linux.cpp b/src/corelib/io/qioring_linux.cpp
new file mode 100644
index 00000000000..b296b916c81
--- /dev/null
+++ b/src/corelib/io/qioring_linux.cpp
@@ -0,0 +1,743 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#include "qioring_p.h"
+
+QT_REQUIRE_CONFIG(liburing);
+
+#include <QtCore/qobject.h>
+#include <QtCore/qscopedvaluerollback.h>
+#include <QtCore/private/qcore_unix_p.h>
+#include <QtCore/private/qfiledevice_p.h>
+
+#include <liburing.h>
+#include <sys/mman.h>
+#include <sys/eventfd.h>
+#include <sys/stat.h>
+
+QT_BEGIN_NAMESPACE
+// From man write.2:
+// On Linux, write() (and similar system calls) will transfer at most 0x7ffff000 (2,147,479,552)
+// bytes, returning the number of bytes actually transferred. (This is true on both 32-bit and
+// 64-bit systems.)
+constexpr qsizetype MaxReadWriteLen = 0x7ffff000; // aka. MAX_RW_COUNT
+
+// We pretend that iovec and QSpans are the same, assert that size and alignment match:
+static_assert(sizeof(iovec)
+ == sizeof(decltype(std::declval<QIORingRequest<QIORing::Operation::VectoredRead>>()
+ .destinations)));
+static_assert(alignof(iovec)
+ == alignof(decltype(std::declval<QIORingRequest<QIORing::Operation::VectoredRead>>()
+ .destinations)));
+
+static io_uring_op toUringOp(QIORing::Operation op);
+static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request,
+ const void *address, qsizetype size);
+
+
+QIORing *QIORing::sharedInstance()
+{
+ thread_local QIORing instance;
+ if (!instance.initializeIORing())
+ return nullptr;
+ return &instance;
+}
+
+QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize)
+ : sqEntries(submissionQueueSize), cqEntries(completionQueueSize)
+{
+}
+QIORing::~QIORing()
+{
+ if (eventDescriptor != -1)
+ close(eventDescriptor);
+ if (io_uringFd != -1)
+ close(io_uringFd);
+}
+
+bool QIORing::initializeIORing()
+{
+ if (io_uringFd != -1)
+ return true;
+
+ io_uring_params params{};
+ params.flags = IORING_SETUP_CQSIZE;
+ params.cq_entries = cqEntries;
+ const int fd = io_uring_setup(sqEntries, &params);
+ if (fd < 0) {
+ qErrnoWarning(-fd, "Failed to setup io_uring");
+ return false;
+ }
+ io_uringFd = fd;
+ size_t submissionQueueSize = params.sq_off.array + (params.sq_entries * sizeof(quint32));
+ size_t completionQueueSize = params.cq_off.cqes + (params.cq_entries * sizeof(io_uring_cqe));
+ if (params.features & IORING_FEAT_SINGLE_MMAP)
+ submissionQueueSize = std::max(submissionQueueSize, completionQueueSize);
+ submissionQueue = mmap(nullptr, submissionQueueSize, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_SQ_RING);
+ if (submissionQueue == MAP_FAILED) {
+ qErrnoWarning(errno, "Failed to mmap io_uring submission queue");
+ close(io_uringFd);
+ io_uringFd = -1;
+ return false;
+ }
+ const size_t submissionQueueEntriesSize = params.sq_entries * sizeof(io_uring_sqe);
+ submissionQueueEntries = static_cast<io_uring_sqe *>(
+ mmap(nullptr, submissionQueueEntriesSize, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_SQES));
+ if (submissionQueueEntries == MAP_FAILED) {
+ qErrnoWarning(errno, "Failed to mmap io_uring submission queue entries");
+ munmap(submissionQueue, submissionQueueSize);
+ close(io_uringFd);
+ io_uringFd = -1;
+ return false;
+ }
+ void *completionQueue = nullptr;
+ if (params.features & IORING_FEAT_SINGLE_MMAP) {
+ completionQueue = submissionQueue;
+ } else {
+ completionQueue = mmap(nullptr, completionQueueSize, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_CQ_RING);
+ if (completionQueue == MAP_FAILED) {
+ qErrnoWarning(errno, "Failed to mmap io_uring completion queue");
+ munmap(submissionQueue, submissionQueueSize);
+ munmap(submissionQueueEntries, submissionQueueEntriesSize);
+ close(io_uringFd);
+ io_uringFd = -1;
+ return false;
+ }
+ }
+ sqEntries = params.sq_entries;
+ cqEntries = params.cq_entries;
+
+ char *sq = static_cast<char *>(submissionQueue);
+ sqHead = reinterpret_cast<quint32 *>(sq + params.sq_off.head);
+ sqTail = reinterpret_cast<quint32 *>(sq + params.sq_off.tail);
+ sqIndexMask = reinterpret_cast<quint32 *>(sq + params.sq_off.ring_mask);
+ sqIndexArray = reinterpret_cast<quint32 *>(sq + params.sq_off.array);
+
+ char *cq = static_cast<char *>(completionQueue);
+ cqHead = reinterpret_cast<quint32 *>(cq + params.cq_off.head);
+ cqTail = reinterpret_cast<quint32 *>(cq + params.cq_off.tail);
+ cqIndexMask = reinterpret_cast<quint32 *>(cq + params.cq_off.ring_mask);
+ completionQueueEntries = reinterpret_cast<io_uring_cqe *>(cq + params.cq_off.cqes);
+
+ eventDescriptor = eventfd(0, 0);
+ io_uring_register(io_uringFd, IORING_REGISTER_EVENTFD, &eventDescriptor, 1);
+
+ notifier.emplace(eventDescriptor, QSocketNotifier::Read);
+ QObject::connect(std::addressof(*notifier), &QSocketNotifier::activated,
+ std::addressof(*notifier), [this]() { completionReady(); });
+ return true;
+}
+
+template <QIORing::Operation Op>
+Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleReadCompletion(const io_uring_cqe *cqe, GenericRequestType *request)
+{
+ auto *readRequest = request->requestData<Op>();
+ Q_ASSERT(readRequest);
+ auto *destinations = [&readRequest]() {
+ if constexpr (Op == Operation::Read)
+ return &readRequest->destination;
+ else
+ return &readRequest->destinations[0];
+ }();
+
+ if (cqe->res < 0) {
+ if (-cqe->res == ECANCELED)
+ readRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::AbortError);
+ else
+ readRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::ReadError);
+ } else if (auto *extra = request->getExtra<QtPrivate::ReadWriteExtra>()) {
+ const qint32 bytesRead = cqe->res;
+ qCDebug(lcQIORing) << "Partial read of" << bytesRead << "bytes completed";
+ auto &readResult = [&readRequest]() -> QIORingResult<Op> & {
+ if (auto *result = std::get_if<QIORingResult<Op>>(&readRequest->result))
+ return *result;
+ return readRequest->result.template emplace<QIORingResult<Op>>();
+ }();
+ readResult.bytesRead += bytesRead;
+ extra->spanOffset += qsizetype(bytesRead);
+ qCDebug(lcQIORing) << "Read operation progress: span" << extra->spanIndex << "offset"
+ << extra->spanOffset << "of" << destinations[extra->spanIndex].size()
+ << "bytes. Total read:" << readResult.bytesRead << "bytes";
+ // The while loop is in case there is an empty span, we skip over it:
+ while (extra->spanOffset == destinations[extra->spanIndex].size()) {
+ // Move to next span
+ if (++extra->spanIndex == extra->numSpans) {
+ --ongoingSplitOperations;
+ return ReadWriteStatus::Finished;
+ }
+ extra->spanOffset = 0;
+ }
+
+ QSpan<std::byte> span = destinations[extra->spanIndex].subspan(extra->spanOffset);
+ if (span.size() > MaxReadWriteLen)
+ span = span.first(MaxReadWriteLen);
+
+ // Move the request such that it is next in the list to be processed:
+ auto &it = addrItMap[request];
+ const auto where = lastUnqueuedIterator.value_or(pendingRequests.end());
+ pendingRequests.splice(where, pendingRequests, it);
+ it = std::prev(where);
+ lastUnqueuedIterator = it;
+
+ return ReadWriteStatus::MoreToDo;
+ } else {
+ auto &result = readRequest->result.template emplace<QIORingResult<Op>>();
+ result.bytesRead = cqe->res;
+ }
+ return ReadWriteStatus::Finished;
+}
+
+template <QIORing::Operation Op>
+Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleWriteCompletion(const io_uring_cqe *cqe, GenericRequestType *request)
+{
+ auto *writeRequest = request->requestData<Op>();
+ Q_ASSERT(writeRequest);
+ auto *sources = [&writeRequest]() {
+ if constexpr (Op == Operation::Write)
+ return &writeRequest->source;
+ else
+ return &writeRequest->sources[0];
+ }();
+
+ if (cqe->res < 0) {
+ if (-cqe->res == ECANCELED)
+ writeRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::AbortError);
+ else
+ writeRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::WriteError);
+ } else if (auto *extra = request->getExtra<QtPrivate::ReadWriteExtra>()) {
+ const qint32 bytesWritten = cqe->res;
+ qCDebug(lcQIORing) << "Partial write of" << bytesWritten << "bytes completed";
+ auto &writeResult = [&writeRequest]() -> QIORingResult<Op> & {
+ if (auto *result = std::get_if<QIORingResult<Op>>(&writeRequest->result))
+ return *result;
+ return writeRequest->result.template emplace<QIORingResult<Op>>();
+ }();
+ writeResult.bytesWritten += bytesWritten;
+ extra->spanOffset += qsizetype(bytesWritten);
+ qCDebug(lcQIORing) << "Write operation progress: span" << extra->spanIndex << "offset"
+ << extra->spanOffset << "of" << sources[extra->spanIndex].size()
+ << "bytes. Total written:" << writeResult.bytesWritten << "bytes";
+ // The while loop is in case there is an empty span, we skip over it:
+ while (extra->spanOffset == sources[extra->spanIndex].size()) {
+ // Move to next span
+ if (++extra->spanIndex == extra->numSpans) {
+ --ongoingSplitOperations;
+ return ReadWriteStatus::Finished;
+ }
+ extra->spanOffset = 0;
+ }
+
+ QSpan<const std::byte> span = sources[extra->spanIndex].subspan(extra->spanOffset);
+ if (span.size() > MaxReadWriteLen)
+ span = span.first(MaxReadWriteLen);
+
+ // Move the request such that it is next in the list to be processed:
+ auto &it = addrItMap[request];
+ const auto where = lastUnqueuedIterator.value_or(pendingRequests.end());
+ pendingRequests.splice(where, pendingRequests, it);
+ it = std::prev(where);
+ lastUnqueuedIterator = it;
+
+ return ReadWriteStatus::MoreToDo;
+ } else {
+ auto &result = writeRequest->result.template emplace<QIORingResult<Op>>();
+ result.bytesWritten = cqe->res;
+ }
+ return ReadWriteStatus::Finished;
+}
+
+void QIORing::completionReady()
+{
+ // Drain the eventfd:
+ [[maybe_unused]]
+ quint64 ignored = 0;
+ std::ignore = read(eventDescriptor, &ignored, sizeof(ignored));
+
+ quint32 head = __atomic_load_n(cqHead, __ATOMIC_RELAXED);
+ const quint32 tail = __atomic_load_n(cqTail, __ATOMIC_ACQUIRE);
+ if (tail == head)
+ return;
+
+ qCDebug(lcQIORing,
+ "Status of completion queue, total entries: %u, tail: %u, head: %u, to process: %u",
+ cqEntries, tail, head, (tail - head));
+ while (head != tail) {
+ /* Get the entry */
+ const io_uring_cqe *cqe = &completionQueueEntries[head & *cqIndexMask];
+ ++head;
+ GenericRequestType *request = reinterpret_cast<GenericRequestType *>(cqe->user_data);
+ qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation()
+ << "- user_data pointer:" << request;
+ switch (request->operation()) {
+ case Operation::Open: {
+ QIORingRequest<Operation::Open>
+ openRequest = request->template takeRequestData<Operation::Open>();
+ if (cqe->res < 0) {
+ // qErrnoWarning(-cqe->res, "Failed to open");
+ if (-cqe->res == ECANCELED)
+ openRequest.result.template emplace<QFileDevice::FileError>(
+ QFileDevice::AbortError);
+ else
+ openRequest.result.template emplace<QFileDevice::FileError>(
+ QFileDevice::OpenError);
+ } else {
+ auto &result = openRequest.result
+ .template emplace<QIORingResult<Operation::Open>>();
+ result.fd = cqe->res;
+ }
+ invokeCallback(openRequest);
+ break;
+ }
+ case Operation::Close: {
+ QIORingRequest<Operation::Close>
+ closeRequest = request->template takeRequestData<Operation::Close>();
+ if (cqe->res < 0) {
+ closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
+ } else {
+ closeRequest.result.emplace<QIORingResult<Operation::Close>>();
+ }
+ invokeCallback(closeRequest);
+ break;
+ }
+ case Operation::Read: {
+ const ReadWriteStatus status = handleReadCompletion<Operation::Read>(cqe, request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto readRequest = request->takeRequestData<Operation::Read>();
+ invokeCallback(readRequest);
+ break;
+ }
+ case Operation::Write: {
+ const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(cqe, request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto writeRequest = request->takeRequestData<Operation::Write>();
+ invokeCallback(writeRequest);
+ break;
+ }
+ case Operation::VectoredRead: {
+ const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(cqe, request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto readvRequest = request->takeRequestData<Operation::VectoredRead>();
+ invokeCallback(readvRequest);
+ break;
+ }
+ case Operation::VectoredWrite: {
+ const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(cqe, request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto writevRequest = request->takeRequestData<Operation::VectoredWrite>();
+ invokeCallback(writevRequest);
+ break;
+ }
+ case Operation::Flush: {
+ QIORingRequest<Operation::Flush>
+ flushRequest = request->template takeRequestData<Operation::Flush>();
+ if (cqe->res < 0) {
+ flushRequest.result.emplace<QFileDevice::FileError>(QFileDevice::WriteError);
+ } else {
+ // No members to fill out, so just initialize to indicate success
+ flushRequest.result.emplace<QIORingResult<Operation::Flush>>();
+ }
+ flushInProgress = false;
+ invokeCallback(flushRequest);
+ break;
+ }
+ case Operation::Cancel: {
+ QIORingRequest<Operation::Cancel>
+ cancelRequest = request->template takeRequestData<Operation::Cancel>();
+ invokeCallback(cancelRequest);
+ break;
+ }
+ case Operation::Stat: {
+ QIORingRequest<Operation::Stat>
+ statRequest = request->template takeRequestData<Operation::Stat>();
+ if (cqe->res < 0) {
+ statRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
+ } else {
+ struct statx *st = request->getExtra<struct statx>();
+ Q_ASSERT(st);
+ auto &res = statRequest.result.emplace<QIORingResult<Operation::Stat>>();
+ res.size = st->stx_size;
+ }
+ invokeCallback(statRequest);
+ break;
+ }
+ case Operation::NumOperations:
+ Q_UNREACHABLE_RETURN();
+ break;
+ }
+ --inFlightRequests;
+ auto it = addrItMap.take(request);
+ pendingRequests.erase(it);
+ }
+ __atomic_store_n(cqHead, head, __ATOMIC_RELEASE);
+ qCDebug(lcQIORing,
+ "Done processing available completions, updated pointers, tail: %u, head: %u", tail,
+ head);
+ prepareRequests();
+ if (!stagePending && unstagedRequests > 0)
+ submitRequests();
+}
+
+bool QIORing::waitForCompletions(QDeadlineTimer deadline)
+{
+ notifier->setEnabled(false);
+ auto reactivateNotifier = qScopeGuard([this]() {
+ notifier->setEnabled(true);
+ });
+
+ pollfd pfd = qt_make_pollfd(eventDescriptor, POLLIN);
+ return qt_safe_poll(&pfd, 1, deadline) > 0;
+}
+
+bool QIORing::supportsOperation(Operation op)
+{
+ switch (op) {
+ case QtPrivate::Operation::Open:
+ case QtPrivate::Operation::Close:
+ case QtPrivate::Operation::Read:
+ case QtPrivate::Operation::Write:
+ case QtPrivate::Operation::VectoredRead:
+ case QtPrivate::Operation::VectoredWrite:
+ case QtPrivate::Operation::Flush:
+ case QtPrivate::Operation::Cancel:
+ case QtPrivate::Operation::Stat:
+ return true;
+ case QtPrivate::Operation::NumOperations:
+ return false;
+ }
+ return false; // May not always be unreachable!
+}
+
+void QIORing::submitRequests()
+{
+ stagePending = false;
+ if (unstagedRequests == 0)
+ return;
+
+ auto submitToRing = [this] {
+ int ret = io_uring_enter(io_uringFd, unstagedRequests, 0, 0, nullptr);
+ if (ret < 0)
+ qErrnoWarning("Error occurred notifying kernel about requests...");
+ else
+ unstagedRequests -= ret;
+ qCDebug(lcQIORing) << "io_uring_enter returned" << ret;
+ return ret >= 0;
+ };
+ if (submitToRing()) {
+ prepareRequests();
+ if (unstagedRequests)
+ submitToRing();
+ }
+}
+
+namespace QtPrivate {
+template <typename T>
+using DetectFd = decltype(std::declval<const T &>().fd);
+
+template <typename T>
+constexpr bool HasFdMember = qxp::is_detected_v<DetectFd, T>;
+} // namespace QtPrivate
+
+bool QIORing::verifyFd(QIORing::GenericRequestType &req)
+{
+ bool result = true;
+ invokeOnOp(req, [&](auto *request) {
+ if constexpr (QtPrivate::HasFdMember<decltype(*request)>) {
+ result = request->fd > 0;
+ }
+ });
+ return result;
+}
+
+void QIORing::prepareRequests()
+{
+ if (!lastUnqueuedIterator) {
+ qCDebug(lcQIORing, "Nothing left to queue");
+ return;
+ }
+ Q_ASSERT(!preparingRequests);
+ QScopedValueRollback<bool> prepareGuard(preparingRequests, true);
+
+ quint32 tail = __atomic_load_n(sqTail, __ATOMIC_RELAXED);
+ const quint32 head = __atomic_load_n(sqHead, __ATOMIC_ACQUIRE);
+ qCDebug(lcQIORing,
+ "Status of submission queue, total entries: %u, tail: %u, head: %u, free: %u",
+ sqEntries, tail, head, sqEntries - (tail - head));
+
+ auto it = *lastUnqueuedIterator;
+ lastUnqueuedIterator.reset();
+ const auto end = pendingRequests.end();
+ bool anyQueued = false;
+ // Loop until we either:
+ // 1. Run out of requests to prepare for submission (it == end),
+ // 2. Have filled the submission queue (unstagedRequests == sqEntries) or,
+ // 3. The number of staged requests + currently processing/potentially finished requests is
+ // enough to fill the completion queue (inFlightRequests == cqEntries).
+ while (!flushInProgress && unstagedRequests != sqEntries && inFlightRequests != cqEntries
+ && it != end) {
+ const quint32 index = tail & *sqIndexMask;
+ io_uring_sqe *sqe = &submissionQueueEntries[index];
+ *sqe = {};
+ RequestPrepResult result = prepareRequest(sqe, *it);
+
+ // QueueFull is unused on Linux:
+ Q_ASSERT(result != RequestPrepResult::QueueFull);
+ if (result == RequestPrepResult::Defer) {
+ qCDebug(lcQIORing) << "Request for" << it->operation()
+ << "had to be deferred, will not queue any more requests at the moment.";
+ break;
+ }
+ if (result == RequestPrepResult::RequestCompleted) {
+ addrItMap.remove(std::addressof(*it));
+ it = pendingRequests.erase(it); // Completed synchronously, either failure or success.
+ continue;
+ }
+ anyQueued = true;
+ it->setQueued(true);
+
+ sqIndexArray[index] = index;
+ ++inFlightRequests;
+ ++unstagedRequests;
+ ++tail;
+ ++it;
+ }
+ if (it != end)
+ lastUnqueuedIterator = it;
+
+ if (anyQueued) {
+ qCDebug(lcQIORing, "Queued %u operation(s)",
+ tail - __atomic_load_n(sqTail, __ATOMIC_RELAXED));
+ __atomic_store_n(sqTail, tail, __ATOMIC_RELEASE);
+ }
+}
+
+static io_uring_op toUringOp(QIORing::Operation op)
+{
+ switch (op) {
+ case QIORing::Operation::Open:
+ return IORING_OP_OPENAT;
+ case QIORing::Operation::Read:
+ return IORING_OP_READ;
+ case QIORing::Operation::Close:
+ return IORING_OP_CLOSE;
+ case QIORing::Operation::Write:
+ return IORING_OP_WRITE;
+ case QIORing::Operation::VectoredRead:
+ return IORING_OP_READV;
+ case QIORing::Operation::VectoredWrite:
+ return IORING_OP_WRITEV;
+ case QIORing::Operation::Flush:
+ return IORING_OP_FSYNC;
+ case QIORing::Operation::Cancel:
+ return IORING_OP_ASYNC_CANCEL;
+ case QIORing::Operation::Stat:
+ return IORING_OP_STATX;
+ case QIORing::Operation::NumOperations:
+ break;
+ }
+ Q_UNREACHABLE_RETURN(IORING_OP_NOP);
+}
+
+Q_ALWAYS_INLINE
+static void prepareFileIOCommon(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request)
+{
+ sqe->fd = qint32(request.fd);
+ sqe->off = request.offset;
+}
+
+Q_ALWAYS_INLINE
+static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request,
+ const void *address, qsizetype size)
+{
+ prepareFileIOCommon(sqe, request);
+ sqe->len = quint32(size);
+ sqe->addr = quint64(address);
+}
+
+// @todo: stolen from qfsfileengine_unix.cpp
+static inline int openModeToOpenFlags(QIODevice::OpenMode mode)
+{
+ int oflags = QT_OPEN_RDONLY;
+#ifdef QT_LARGEFILE_SUPPORT
+ oflags |= QT_OPEN_LARGEFILE;
+#endif
+
+ if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite)
+ oflags = QT_OPEN_RDWR;
+ else if (mode & QIODevice::WriteOnly)
+ oflags = QT_OPEN_WRONLY;
+
+ if ((mode & QIODevice::WriteOnly)
+ && !(mode & QIODevice::ExistingOnly)) // QFSFileEnginePrivate::openModeCanCreate(mode))
+ oflags |= QT_OPEN_CREAT;
+
+ if (mode & QIODevice::Truncate)
+ oflags |= QT_OPEN_TRUNC;
+
+ if (mode & QIODevice::Append)
+ oflags |= QT_OPEN_APPEND;
+
+ if (mode & QIODevice::NewOnly)
+ oflags |= QT_OPEN_EXCL;
+
+ return oflags;
+}
+
+auto QIORing::prepareRequest(io_uring_sqe *sqe, GenericRequestType &request) -> RequestPrepResult
+{
+ sqe->user_data = qint64(&request);
+ sqe->opcode = toUringOp(request.operation());
+
+ if (!verifyFd(request)) {
+ finishRequestWithError(request, QFileDevice::OpenError);
+ return RequestPrepResult::RequestCompleted;
+ }
+
+ switch (request.operation()) {
+ case Operation::Open: {
+ const QIORingRequest<Operation::Open>
+ *openRequest = request.template requestData<Operation::Open>();
+ sqe->fd = AT_FDCWD; // Could also support proper openat semantics
+ sqe->addr = reinterpret_cast<quint64>(openRequest->path.native().c_str());
+ sqe->open_flags = openModeToOpenFlags(openRequest->flags);
+ auto &mode = sqe->len;
+ mode = 0666; // With an explicit API we can use QtPrivate::toMode_t() for this
+ break;
+ }
+ case Operation::Close: {
+ if (ongoingSplitOperations)
+ return Defer;
+ const QIORingRequest<Operation::Close>
+ *closeRequest = request.template requestData<Operation::Close>();
+ sqe->fd = closeRequest->fd;
+ // Force all earlier entries in the sq to finish before this is processed:
+ sqe->flags |= IOSQE_IO_DRAIN;
+ break;
+ }
+ case Operation::Read: {
+ const QIORingRequest<Operation::Read>
+ *readRequest = request.template requestData<Operation::Read>();
+ auto span = readRequest->destination;
+ if (span.size() >= MaxReadWriteLen) {
+ auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
+ qsizetype remaining = span.size() - extra->spanOffset;
+ span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
+ ++ongoingSplitOperations;
+ }
+ prepareFileReadWrite(sqe, *readRequest, span.data(), span.size());
+ break;
+ }
+ case Operation::Write: {
+ const QIORingRequest<Operation::Write>
+ *writeRequest = request.template requestData<Operation::Write>();
+ auto span = writeRequest->source;
+ if (span.size() >= MaxReadWriteLen) {
+ auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
+ qsizetype remaining = span.size() - extra->spanOffset;
+ span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
+ ++ongoingSplitOperations;
+ }
+ prepareFileReadWrite(sqe, *writeRequest, span.data(), span.size());
+ break;
+ }
+ case Operation::VectoredRead: {
+ // @todo Apply the split read/write concept that will apply above to this too
+ const QIORingRequest<Operation::VectoredRead>
+ *readvRequest = request.template requestData<Operation::VectoredRead>();
+ prepareFileReadWrite(sqe, *readvRequest, readvRequest->destinations.data(),
+ readvRequest->destinations.size());
+ break;
+ }
+ case Operation::VectoredWrite: {
+ // @todo Apply the split read/write concept that will apply above to this too
+ const QIORingRequest<Operation::VectoredWrite>
+ *writevRequest = request.template requestData<Operation::VectoredWrite>();
+ prepareFileReadWrite(sqe, *writevRequest, writevRequest->sources.data(),
+ writevRequest->sources.size());
+ break;
+ }
+ case Operation::Flush: {
+ if (ongoingSplitOperations)
+ return Defer;
+ const QIORingRequest<Operation::Flush>
+ *flushRequest = request.template requestData<Operation::Flush>();
+ sqe->fd = qint32(flushRequest->fd);
+ // Force all earlier entries in the sq to finish before this is processed:
+ sqe->flags |= IOSQE_IO_DRAIN;
+ flushInProgress = true;
+ break;
+ }
+ case Operation::Cancel: {
+ const QIORingRequest<Operation::Cancel>
+ *cancelRequest = request.template requestData<Operation::Cancel>();
+ auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle);
+ auto it = std::as_const(addrItMap).find(otherOperation);
+ if (it == addrItMap.cend()) { // : The request to cancel doesn't exist
+ invokeCallback(*cancelRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ if (!otherOperation->wasQueued()) {
+ // The request hasn't been queued yet, so we can just drop it from
+ // the pending requests and call the callback.
+ Q_ASSERT(!lastUnqueuedIterator);
+ finishRequestWithError(*otherOperation, QFileDevice::AbortError);
+ pendingRequests.erase(*it); // otherOperation is deleted
+ addrItMap.erase(it);
+ invokeCallback(*cancelRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ sqe->addr = quint64(otherOperation);
+ break;
+ }
+ case Operation::Stat: {
+ const QIORingRequest<Operation::Stat>
+ *statRequest = request.template requestData<Operation::Stat>();
+ // We need to store the statx struct somewhere:
+ struct statx *st = request.getOrInitializeExtra<struct statx>();
+
+ sqe->fd = statRequest->fd;
+ // We want to use the fd as the target of query instead of as the fd of the relative dir,
+ // so we set addr to an empty string, and specify the AT_EMPTY_PATH flag.
+ static const char emptystr[] = "";
+ sqe->addr = qint64(emptystr);
+ sqe->statx_flags = AT_EMPTY_PATH;
+ sqe->len = STATX_ALL; // @todo configure somehow
+ sqe->off = quint64(st);
+ break;
+ }
+ case Operation::NumOperations:
+ Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted);
+ break;
+ }
+ return RequestPrepResult::Ok;
+}
+
+void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra)
+{
+ switch (op) {
+ case Operation::Open:
+ case Operation::Close:
+ case Operation::VectoredRead:
+ case Operation::VectoredWrite:
+ case Operation::Cancel:
+ case Operation::Flush:
+ case Operation::NumOperations:
+ break;
+ case Operation::Read:
+ case Operation::Write:
+ delete static_cast<QtPrivate::ReadWriteExtra *>(extra);
+ return;
+ case Operation::Stat:
+ delete static_cast<struct statx *>(extra);
+ return;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/io/qioring_p.h b/src/corelib/io/qioring_p.h
new file mode 100644
index 00000000000..d4c4308122e
--- /dev/null
+++ b/src/corelib/io/qioring_p.h
@@ -0,0 +1,484 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#ifndef IOPROCESSOR_P_H
+#define IOPROCESSOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qtcoreglobal_p.h>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qspan.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qfiledevice.h>
+#include <QtCore/qwineventnotifier.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdeadlinetimer.h>
+
+#ifdef Q_OS_LINUX
+# include <QtCore/qsocketnotifier.h>
+struct io_uring_sqe;
+struct io_uring_cqe;
+#endif
+
+#include <algorithm>
+#include <filesystem>
+#include <variant>
+#include <optional>
+#include <type_traits>
+
+/*
+ This file defines an interface for the backend of QRandomAccessFile.
+ The backends themselves are implemented in platform-specific files, such as
+ ioring_linux.cpp, ioring_win.cpp, etc.
+ And has a lower-level interface than the public interface will have, but the
+ separation hopefully makes it easier to implement the ioring backends, test
+ them, and tweak them without the higher-level interface needing to see
+ changes, and to make it possible to tweak the higher-level interface without
+ needing to touch the (somewhat similar) ioring backends.
+
+ Most of the interface is just an enum QIORing::Operation + the
+ QIORingRequest template class, which is specialized for each operation so it
+ carries just the relevant data for that operation. And a small mechanism to
+ store the request in a generic manner so they can be used in the
+ implementation files at the cost of some overhead.
+
+ There will be absolutely zero binary compatibility guarantees for this
+ interface.
+*/
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQIORing);
+
+namespace QtPrivate {
+Q_NAMESPACE
+
+#define FOREACH_IO_OPERATION(OP) \
+ OP(Open) \
+ OP(Close) \
+ OP(Read) \
+ OP(Write) \
+ OP(VectoredRead) \
+ OP(VectoredWrite) \
+ OP(Flush) \
+ OP(Stat) \
+ OP(Cancel) \
+ /**/
+#define DEFINE_ENTRY(OP) OP,
+
+// clang-format off
+enum class Operation : quint8 {
+ FOREACH_IO_OPERATION(DEFINE_ENTRY)
+
+ NumOperations,
+};
+// clang-format on
+Q_ENUM_NS(Operation);
+#undef DEFINE_ENTRY
+}; // namespace QtPrivate
+
+template <QtPrivate::Operation Op>
+struct QIORingRequest;
+
+class QIORing final
+{
+ class GenericRequestType;
+ struct RequestHandleTag; // Just used as an opaque pointer
+public:
+ static constexpr quint32 DefaultSubmissionQueueSize = 128;
+ static constexpr quint32 DefaultCompletionQueueSize = DefaultSubmissionQueueSize * 2;
+ using Operation = QtPrivate::Operation;
+ using RequestHandle = RequestHandleTag *;
+
+ Q_CORE_EXPORT
+ explicit QIORing(quint32 submissionQueueSize = DefaultSubmissionQueueSize,
+ quint32 completionQueueSize = DefaultCompletionQueueSize);
+ Q_CORE_EXPORT
+ ~QIORing();
+ Q_DISABLE_COPY_MOVE(QIORing)
+
+ Q_CORE_EXPORT
+ static QIORing *sharedInstance();
+ bool ensureInitialized() { return initializeIORing(); }
+
+ Q_CORE_EXPORT
+ static bool supportsOperation(Operation op);
+ template <Operation Op>
+ QIORing::RequestHandle queueRequest(QIORingRequest<Op> &&request)
+ {
+ Q_ASSERT(supportsOperation(Op));
+ auto &r = pendingRequests.emplace_back(std::move(request));
+ addrItMap.emplace(&r, std::prev(pendingRequests.end()));
+ if (queueRequestInternal(r) == QueuedRequestStatus::CompletedImmediately)
+ return nullptr; // Return an invalid handle, to avoid ABA with following requests
+ return reinterpret_cast<RequestHandle>(&r);
+ }
+ Q_CORE_EXPORT
+ void submitRequests();
+ Q_CORE_EXPORT
+ bool waitForRequest(RequestHandle handle, QDeadlineTimer deadline = QDeadlineTimer::Forever);
+
+ quint32 submissionQueueSize() const noexcept { return sqEntries; }
+ quint32 completionQueueSize() const noexcept { return cqEntries; }
+
+private:
+ std::list<GenericRequestType> pendingRequests;
+ using PendingRequestsIterator = decltype(pendingRequests.begin());
+ QHash<void *, PendingRequestsIterator> addrItMap;
+ std::optional<PendingRequestsIterator> lastUnqueuedIterator;
+ quint32 sqEntries = 0;
+ quint32 cqEntries = 0;
+ quint32 inFlightRequests = 0;
+ quint32 unstagedRequests = 0;
+ bool stagePending = false;
+ bool preparingRequests = false;
+ qsizetype ongoingSplitOperations = 0;
+
+ Q_CORE_EXPORT
+ bool initializeIORing();
+
+ enum class QueuedRequestStatus : bool {
+ Pending = false,
+ CompletedImmediately = true,
+ };
+ Q_CORE_EXPORT
+ QueuedRequestStatus queueRequestInternal(GenericRequestType &request);
+ void prepareRequests();
+ void completionReady();
+ bool waitForCompletions(QDeadlineTimer deadline);
+
+ template <typename Fun>
+ static auto invokeOnOp(GenericRequestType &req, Fun fn);
+
+ static void finishRequestWithError(GenericRequestType &req, QFileDevice::FileError error);
+ static bool verifyFd(GenericRequestType &req);
+
+ enum RequestPrepResult : quint8 {
+ Ok,
+ QueueFull,
+ Defer,
+ RequestCompleted,
+ };
+ enum class ReadWriteStatus : bool {
+ MoreToDo,
+ Finished,
+ };
+#ifdef Q_OS_LINUX
+ std::optional<QSocketNotifier> notifier;
+ // io_uring 'sq', 'sqe', 'cq', and 'cqe' pointers:
+ void *submissionQueue = nullptr;
+ io_uring_sqe *submissionQueueEntries = nullptr;
+ const io_uring_cqe *completionQueueEntries = nullptr;
+
+ // Some pointers for working with the ring-buffer.
+ // The pointers to const are controlled by the kernel.
+ const quint32 *sqHead = nullptr;
+ quint32 *sqTail = nullptr;
+ const quint32 *sqIndexMask = nullptr;
+ quint32 *sqIndexArray = nullptr;
+ quint32 *cqHead = nullptr;
+ const quint32 *cqTail = nullptr;
+ const quint32 *cqIndexMask = nullptr;
+ // Because we want the flush to act as a barrier operation we need to track
+ // if there is one currently in progress. With kernel 6.16+ this seems to be
+ // fixed, but since we support older kernels we implement this deferring
+ // ourselves.
+ bool flushInProgress = false;
+
+ int io_uringFd = -1;
+ int eventDescriptor = -1;
+ [[nodiscard]]
+ RequestPrepResult prepareRequest(io_uring_sqe *sqe, GenericRequestType &request);
+ template <Operation Op>
+ ReadWriteStatus handleReadCompletion(const io_uring_cqe *cqe, GenericRequestType *request);
+ template <Operation Op>
+ ReadWriteStatus handleWriteCompletion(const io_uring_cqe *cqe, GenericRequestType *request);
+#endif
+};
+
+struct QIORingRequestEmptyBase
+{
+};
+
+template <QtPrivate::Operation Op>
+struct QIORingResult;
+template <QtPrivate::Operation Op>
+struct QIORingRequest;
+
+// @todo: q23::expected once emplace() returns a reference
+template <QtPrivate::Operation Op>
+using ExpectedResultType = std::variant<std::monostate, QIORingResult<Op>, QFileDevice::FileError>;
+
+struct QIORingRequestOffsetFdBase : QIORingRequestEmptyBase
+{
+ qintptr fd;
+ quint64 offset;
+};
+
+template <QtPrivate::Operation Op, typename Base = QIORingRequestOffsetFdBase>
+struct QIORingRequestBase : Base
+{
+ ExpectedResultType<Op> result; // To be filled in by the backend
+ QtPrivate::SlotObjUniquePtr callback;
+ template <typename Func>
+ Q_ALWAYS_INLINE void setCallback(Func &&func)
+ {
+ using Prototype = void (*)(const QIORingRequest<Op> &);
+ callback.reset(QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(func)));
+ }
+};
+
+template <>
+struct QIORingResult<QtPrivate::Operation::Open>
+{
+ qintptr fd;
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::Open> final
+ : QIORingRequestBase<QtPrivate::Operation::Open, QIORingRequestEmptyBase>
+{
+ std::filesystem::path path;
+ QFileDevice::OpenMode flags;
+};
+template <>
+struct QIORingResult<QtPrivate::Operation::Close>
+{
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::Close> final
+ : QIORingRequestBase<QtPrivate::Operation::Close, QIORingRequestEmptyBase>
+{
+ qintptr fd;
+};
+
+template <>
+struct QIORingResult<QtPrivate::Operation::Write>
+{
+ qint64 bytesWritten;
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::Write> final
+ : QIORingRequestBase<QtPrivate::Operation::Write>
+{
+ QSpan<const std::byte> source;
+};
+template <>
+struct QIORingResult<QtPrivate::Operation::VectoredWrite> final
+ : QIORingResult<QtPrivate::Operation::Write>
+{
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::VectoredWrite> final
+ : QIORingRequestBase<QtPrivate::Operation::VectoredWrite>
+{
+ QSpan<const QSpan<const std::byte>> sources;
+};
+
+template <>
+struct QIORingResult<QtPrivate::Operation::Read>
+{
+ qint64 bytesRead;
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::Read> final
+ : QIORingRequestBase<QtPrivate::Operation::Read>
+{
+ QSpan<std::byte> destination;
+};
+
+template <>
+struct QIORingResult<QtPrivate::Operation::VectoredRead> final
+ : QIORingResult<QtPrivate::Operation::Read>
+{
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::VectoredRead> final
+ : QIORingRequestBase<QtPrivate::Operation::VectoredRead>
+{
+ QSpan<QSpan<std::byte>> destinations;
+};
+
+template <>
+struct QIORingResult<QtPrivate::Operation::Flush> final
+{
+ // No value in the result, just a success or failure
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::Flush> final : QIORingRequestBase<QtPrivate::Operation::Flush, QIORingRequestEmptyBase>
+{
+ qintptr fd;
+};
+
+template <>
+struct QIORingResult<QtPrivate::Operation::Stat> final
+{
+ quint64 size;
+};
+template <>
+struct QIORingRequest<QtPrivate::Operation::Stat> final
+ : QIORingRequestBase<QtPrivate::Operation::Stat, QIORingRequestEmptyBase>
+{
+ qintptr fd;
+};
+
+// This is not inheriting the QIORingRequestBase because it doesn't have a result,
+// whether it was successful or not is indicated by whether the operation
+// it was cancelling was successful or not.
+template <>
+struct QIORingRequest<QtPrivate::Operation::Cancel> final : QIORingRequestEmptyBase
+{
+ QIORing::RequestHandle handle;
+ QtPrivate::SlotObjUniquePtr callback;
+ template <typename Func>
+ Q_ALWAYS_INLINE void setCallback(Func &&func)
+ {
+ using Op = QtPrivate::Operation;
+ using Prototype = void (*)(const QIORingRequest<Op::Cancel> &);
+ callback.reset(QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(func)));
+ }
+};
+
+template <QIORing::Operation Op>
+Q_ALWAYS_INLINE void invokeCallback(const QIORingRequest<Op> &request)
+{
+ if (!request.callback)
+ return;
+ void *args[2] = { nullptr, const_cast<QIORingRequest<Op> *>(&request) };
+ request.callback->call(nullptr, args);
+}
+
+class QIORing::GenericRequestType
+{
+ friend class QIORing;
+
+#define POPULATE_VARIANT(Op) \
+ QIORingRequest<Operation::Op>, \
+ /**/
+
+ std::variant<
+ FOREACH_IO_OPERATION(POPULATE_VARIANT)
+ std::monostate
+ > taggedUnion;
+
+#undef POPULATE_VARIANT
+
+ void *extraData = nullptr;
+ bool queued = false;
+
+ template <Operation Op>
+ Q_ALWAYS_INLINE void initializeStorage(QIORingRequest<Op> &&t) noexcept
+ {
+ static_assert(Op < Operation::NumOperations);
+ taggedUnion.emplace<QIORingRequest<Op>>(std::move(t));
+ }
+
+ Q_CORE_EXPORT
+ static void cleanupExtra(Operation op, void *extra);
+ template <typename T>
+ T *getOrInitializeExtra()
+ {
+ if (!extraData)
+ extraData = new T();
+ return static_cast<T *>(extraData);
+ }
+ template <typename T>
+ T *getExtra() const
+ {
+ return static_cast<T *>(extraData);
+ }
+ void reset() noexcept
+ {
+ Operation op = operation();
+ taggedUnion.emplace<std::monostate>();
+ if (extraData)
+ cleanupExtra(op, std::exchange(extraData, nullptr));
+ }
+
+public:
+ template <Operation Op>
+ explicit GenericRequestType(QIORingRequest<Op> &&t) noexcept
+ {
+ initializeStorage(std::move(t));
+ }
+ ~GenericRequestType() noexcept
+ {
+ reset();
+ }
+ Q_DISABLE_COPY_MOVE(GenericRequestType)
+ // We have to provide equality operators. Since copying is disabled, we just check for equality
+ // based on the address in memory. Two requests could be constructed to be equal, but we don't
+ // actually care because the order in which they are added to the queue may also matter.
+ friend bool operator==(const GenericRequestType &l, const GenericRequestType &r) noexcept
+ {
+ return std::addressof(l) == std::addressof(r);
+ }
+ friend bool operator!=(const GenericRequestType &l, const GenericRequestType &r) noexcept
+ {
+ return !(l == r);
+ }
+
+ Operation operation() const { return Operation(taggedUnion.index()); }
+ template <Operation Op>
+ QIORingRequest<Op> *requestData()
+ {
+ if (operation() == Op)
+ return std::get_if<QIORingRequest<Op>>(&taggedUnion);
+ Q_ASSERT("Wrong operation requested, see operation()");
+ return nullptr;
+ }
+ template <Operation Op>
+ QIORingRequest<Op> takeRequestData()
+ {
+ if (operation() == Op)
+ return std::move(*std::get_if<QIORingRequest<Op>>(&taggedUnion));
+ Q_ASSERT("Wrong operation requested, see operation()");
+ return {};
+ }
+ bool wasQueued() const { return queued; }
+ void setQueued(bool status) { queued = status; }
+};
+
+template <typename Fun>
+auto QIORing::invokeOnOp(GenericRequestType &req, Fun fn)
+{
+#define INVOKE_ON_OP(Op) \
+case QIORing::Operation::Op: \
+ fn(req.template requestData<Operation::Op>()); \
+ return; \
+ /**/
+
+ switch (req.operation()) {
+ FOREACH_IO_OPERATION(INVOKE_ON_OP)
+ case QIORing::Operation::NumOperations:
+ break;
+ }
+
+ Q_UNREACHABLE();
+#undef INVOKE_ON_OP
+}
+
+namespace QtPrivate {
+// The 'extra' struct for Read/Write operations that must be split up
+struct ReadWriteExtra
+{
+ qsizetype spanIndex = 0;
+ qsizetype spanOffset = 0;
+ qsizetype numSpans = 1;
+};
+} // namespace QtPrivate
+
+QT_END_NAMESPACE
+
+#endif // IOPROCESSOR_P_H
diff --git a/src/corelib/io/qrandomaccessasyncfile_p_p.h b/src/corelib/io/qrandomaccessasyncfile_p_p.h
index 924c9f9ed83..11ad788c884 100644
--- a/src/corelib/io/qrandomaccessasyncfile_p_p.h
+++ b/src/corelib/io/qrandomaccessasyncfile_p_p.h
@@ -43,6 +43,11 @@
#endif // Q_OS_DARWIN
+#ifdef QT_RANDOMACCESSASYNCFILE_QIORING
+#include <QtCore/private/qioring_p.h>
+#include <QtCore/qlist.h>
+#endif
+
QT_BEGIN_NAMESPACE
class QRandomAccessAsyncFilePrivate : public QObjectPrivate
@@ -114,6 +119,15 @@ private:
void processFlush();
void processOpen();
void operationComplete();
+#elif defined(QT_RANDOMACCESSASYNCFILE_QIORING)
+ void queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error);
+ void startReadIntoSingle(QIOOperation *op, const QSpan<std::byte> &to);
+ void startWriteFromSingle(QIOOperation *op, const QSpan<const std::byte> &from);
+ QIORing::RequestHandle cancel(QIORing::RequestHandle handle);
+ QIORing *m_ioring = nullptr;
+ qintptr m_fd = -1;
+ QList<QPointer<QIOOperation>> m_operations;
+ QHash<QIOOperation *, QIORing::RequestHandle> m_opHandleMap;
#endif
#ifdef Q_OS_DARWIN
using OperationId = quint64;
diff --git a/src/corelib/io/qrandomaccessasyncfile_qioring.cpp b/src/corelib/io/qrandomaccessasyncfile_qioring.cpp
new file mode 100644
index 00000000000..c9783ea2856
--- /dev/null
+++ b/src/corelib/io/qrandomaccessasyncfile_qioring.cpp
@@ -0,0 +1,438 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Qt-Security score:significant reason:default
+
+#include "qrandomaccessasyncfile_p_p.h"
+
+#include "qiooperation_p.h"
+#include "qiooperation_p_p.h"
+
+#include <QtCore/qfile.h> // QtPrivate::toFilesystemPath
+#include <QtCore/qtypes.h>
+#include <QtCore/private/qioring_p.h>
+
+#include <QtCore/q26numeric.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_STATIC_LOGGING_CATEGORY(lcQRandomAccessIORing, "qt.core.qrandomaccessasyncfile.ioring",
+ QtCriticalMsg);
+
+QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() = default;
+
+QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() = default;
+
+void QRandomAccessAsyncFilePrivate::init()
+{
+ m_ioring = QIORing::sharedInstance();
+ if (!m_ioring)
+ qCCritical(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize");
+}
+
+QIORing::RequestHandle QRandomAccessAsyncFilePrivate::cancel(QIORing::RequestHandle handle)
+{
+ if (handle) {
+ QIORingRequest<QIORing::Operation::Cancel> cancelRequest;
+ cancelRequest.handle = handle;
+ return m_ioring->queueRequest(std::move(cancelRequest));
+ }
+ return nullptr;
+}
+
+void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op)
+{
+ auto *opHandle = m_opHandleMap.value(op);
+ if (auto *handle = cancel(opHandle)) {
+ m_ioring->waitForRequest(handle);
+ m_ioring->waitForRequest(opHandle);
+ }
+}
+
+void QRandomAccessAsyncFilePrivate::queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error)
+{
+ // Remove the handle now in case the user cancels or deletes the io-operation
+ // before operationComplete is called - the null-handle will protect from
+ // nasty issues that may occur when trying to cancel an operation that's no
+ // longer in the queue:
+ m_opHandleMap.remove(priv->q_func());
+ // @todo: Look into making it emit only if synchronously completed
+ QMetaObject::invokeMethod(priv->q_ptr, [priv, error](){
+ priv->operationComplete(error);
+ }, Qt::QueuedConnection);
+}
+
+QIOOperation *QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode)
+{
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage();
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->type = QIOOperation::Type::Open;
+
+ auto *op = new QIOOperation(*priv, q_ptr);
+ if (m_fileState != FileState::Closed) {
+ queueCompletion(priv, QIOOperation::Error::Open);
+ return op;
+ }
+ m_operations.append(op);
+ m_fileState = FileState::OpenPending;
+
+ QIORingRequest<QIORing::Operation::Open> openOperation;
+ openOperation.path = QtPrivate::toFilesystemPath(path);
+ openOperation.flags = mode;
+ openOperation.setCallback([this, op,
+ priv](const QIORingRequest<QIORing::Operation::Open> &request) {
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ if (m_fileState != FileState::Opened) {
+ // We assume there was only one pending open() in flight.
+ m_fd = -1;
+ m_fileState = FileState::Closed;
+ }
+ if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
+ queueCompletion(priv, QIOOperation::Error::Aborted);
+ else
+ queueCompletion(priv, QIOOperation::Error::Open);
+ } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Open>>(
+ &request.result)) {
+ if (m_fileState == FileState::OpenPending) {
+ m_fileState = FileState::Opened;
+ m_fd = result->fd;
+ queueCompletion(priv, QIOOperation::Error::None);
+ } else { // Something went wrong, we did not expect a callback:
+ // So we close the new handle:
+ QIORingRequest<QIORing::Operation::Close> closeRequest;
+ closeRequest.fd = result->fd;
+ QIORing::RequestHandle handle = m_ioring->queueRequest(std::move(closeRequest));
+ // Since the user issued multiple open() calls they get to wait for the close() to
+ // finish:
+ m_ioring->waitForRequest(handle);
+ queueCompletion(priv, QIOOperation::Error::Open);
+ }
+ }
+ m_operations.removeOne(op);
+ });
+ m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(openOperation)));
+
+ return op;
+}
+
+void QRandomAccessAsyncFilePrivate::close()
+{
+ // all the operations should be aborted
+ const auto ops = std::exchange(m_operations, {});
+ QList<QIORing::RequestHandle> tasksToAwait;
+ // Request to cancel all of the in-flight operations:
+ for (const auto &op : ops) {
+ if (op) {
+ op->d_func()->error = QIOOperation::Error::Aborted;
+ if (auto *opHandle = m_opHandleMap.value(op)) {
+ tasksToAwait.append(cancel(opHandle));
+ tasksToAwait.append(opHandle);
+ }
+ }
+ }
+
+ QIORingRequest<QIORing::Operation::Close> closeRequest;
+ closeRequest.fd = m_fd;
+ tasksToAwait.append(m_ioring->queueRequest(std::move(closeRequest)));
+
+ // Wait for completion:
+ for (const QIORing::RequestHandle &handle : tasksToAwait)
+ m_ioring->waitForRequest(handle);
+ m_fileState = FileState::Closed;
+ m_fd = -1;
+}
+
+qint64 QRandomAccessAsyncFilePrivate::size() const
+{
+ QIORingRequest<QIORing::Operation::Stat> statRequest;
+ statRequest.fd = m_fd;
+ qint64 finalSize = 0;
+ statRequest.setCallback([&finalSize](const QIORingRequest<QIORing::Operation::Stat> &request) {
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ Q_UNUSED(err);
+ finalSize = -1;
+ } else if (const auto *res = std::get_if<QIORingResult<QIORing::Operation::Stat>>(&request.result)) {
+ finalSize = q26::saturate_cast<qint64>(res->size);
+ }
+ });
+ auto *handle = m_ioring->queueRequest(std::move(statRequest));
+ m_ioring->waitForRequest(handle);
+
+ return finalSize;
+}
+
+QIOOperation *QRandomAccessAsyncFilePrivate::flush()
+{
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage();
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->type = QIOOperation::Type::Flush;
+
+ auto *op = new QIOOperation(*priv, q_ptr);
+ m_operations.append(op);
+
+ QIORingRequest<QIORing::Operation::Flush> flushRequest;
+ flushRequest.fd = m_fd;
+ flushRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Flush> &request) {
+ auto *priv = QIOOperationPrivate::get(op);
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
+ queueCompletion(priv, QIOOperation::Error::Aborted);
+ else if (*err == QFileDevice::OpenError)
+ queueCompletion(priv, QIOOperation::Error::FileNotOpen);
+ else
+ queueCompletion(priv, QIOOperation::Error::Flush);
+ } else if (std::get_if<QIORingResult<QIORing::Operation::Flush>>(&request.result)) {
+ queueCompletion(priv, QIOOperation::Error::None);
+ }
+ m_operations.removeOne(op);
+ });
+ m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(flushRequest)));
+
+ return op;
+}
+
+void QRandomAccessAsyncFilePrivate::startReadIntoSingle(QIOOperation *op,
+ const QSpan<std::byte> &to)
+{
+ QIORingRequest<QIORing::Operation::Read> readRequest;
+ readRequest.fd = m_fd;
+ auto *priv = QIOOperationPrivate::get(op);
+ if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
+ queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
+ m_operations.removeOne(op);
+ return;
+ }
+ readRequest.offset = priv->offset;
+ readRequest.destination = to;
+ readRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Read> &request) {
+ auto *priv = QIOOperationPrivate::get(op);
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
+ queueCompletion(priv, QIOOperation::Error::Aborted);
+ else if (*err == QFileDevice::OpenError)
+ queueCompletion(priv, QIOOperation::Error::FileNotOpen);
+ else if (*err == QFileDevice::PositionError)
+ queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
+ else
+ queueCompletion(priv, QIOOperation::Error::Read);
+ } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Read>>(
+ &request.result)) {
+ priv->appendBytesProcessed(result->bytesRead);
+ if (priv->dataStorage->containsReadSpans())
+ priv->dataStorage->getReadSpans().first().slice(0, result->bytesRead);
+ else
+ priv->dataStorage->getByteArray().slice(0, result->bytesRead);
+
+ queueCompletion(priv, QIOOperation::Error::None);
+ }
+ m_operations.removeOne(op);
+ });
+ m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest)));
+}
+
+QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxSize)
+{
+ QByteArray array;
+ array.resizeForOverwrite(maxSize);
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(array));
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->offset = offset;
+ priv->type = QIOOperation::Type::Read;
+
+ auto *op = new QIOReadOperation(*priv, q_ptr);
+ m_operations.append(op);
+
+ startReadIntoSingle(op, as_writable_bytes(QSpan(dataStorage->getByteArray())));
+
+ return op;
+}
+
+QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data)
+{
+ return write(offset, QByteArray(data));
+}
+
+void QRandomAccessAsyncFilePrivate::startWriteFromSingle(QIOOperation *op,
+ const QSpan<const std::byte> &from)
+{
+ QIORingRequest<QIORing::Operation::Write> writeRequest;
+ writeRequest.fd = m_fd;
+ auto *priv = QIOOperationPrivate::get(op);
+ if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
+ queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
+ m_operations.removeOne(op);
+ return;
+ }
+ writeRequest.offset = priv->offset;
+ writeRequest.source = from;
+ writeRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Write> &request) {
+ auto *priv = QIOOperationPrivate::get(op);
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
+ queueCompletion(priv, QIOOperation::Error::Aborted);
+ else if (*err == QFileDevice::OpenError)
+ queueCompletion(priv, QIOOperation::Error::FileNotOpen);
+ else if (*err == QFileDevice::PositionError)
+ queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
+ else
+ queueCompletion(priv, QIOOperation::Error::Write);
+ } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Write>>(
+ &request.result)) {
+ priv->appendBytesProcessed(result->bytesWritten);
+ queueCompletion(priv, QIOOperation::Error::None);
+ }
+ m_operations.removeOne(op);
+ });
+ m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest)));
+}
+
+QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data)
+{
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(data));
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->offset = offset;
+ priv->type = QIOOperation::Type::Write;
+
+ auto *op = new QIOWriteOperation(*priv, q_ptr);
+ m_operations.append(op);
+
+ startWriteFromSingle(op, as_bytes(QSpan(dataStorage->getByteArray())));
+
+ return op;
+}
+
+QIOVectoredReadOperation *QRandomAccessAsyncFilePrivate::readInto(qint64 offset,
+ QSpan<std::byte> buffer)
+{
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage(
+ QSpan<const QSpan<std::byte>>{ buffer });
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->offset = offset;
+ priv->type = QIOOperation::Type::Read;
+
+ auto *op = new QIOVectoredReadOperation(*priv, q_ptr);
+ m_operations.append(op);
+
+ startReadIntoSingle(op, dataStorage->getReadSpans().first());
+
+ return op;
+}
+
+QIOVectoredWriteOperation *QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset,
+ QSpan<const std::byte> buffer)
+{
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage(
+ QSpan<const QSpan<const std::byte>>{ buffer });
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->offset = offset;
+ priv->type = QIOOperation::Type::Write;
+
+ auto *op = new QIOVectoredWriteOperation(*priv, q_ptr);
+ m_operations.append(op);
+
+ startWriteFromSingle(op, dataStorage->getWriteSpans().first());
+
+ return op;
+}
+
+QIOVectoredReadOperation *
+QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers)
+{
+ if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredRead))
+ return nullptr;
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers);
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->offset = offset;
+ priv->type = QIOOperation::Type::Read;
+
+ auto *op = new QIOVectoredReadOperation(*priv, q_ptr);
+ if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
+ queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
+ return op;
+ }
+ m_operations.append(op);
+
+ QIORingRequest<QIORing::Operation::VectoredRead> readRequest;
+ readRequest.fd = m_fd;
+ readRequest.offset = priv->offset;
+ readRequest.destinations = dataStorage->getReadSpans();
+ readRequest.setCallback([this,
+ op](const QIORingRequest<QIORing::Operation::VectoredRead> &request) {
+ auto *priv = QIOOperationPrivate::get(op);
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
+ queueCompletion(priv, QIOOperation::Error::Aborted);
+ else
+ queueCompletion(priv, QIOOperation::Error::Read);
+ } else if (const auto
+ *result = std::get_if<QIORingResult<QIORing::Operation::VectoredRead>>(
+ &request.result)) {
+ priv->appendBytesProcessed(result->bytesRead);
+ qint64 processed = result->bytesRead;
+ for (auto &span : priv->dataStorage->getReadSpans()) {
+ if (span.size() < processed) {
+ processed -= span.size();
+ } else { // span.size >= processed
+ span.slice(0, processed);
+ processed = 0;
+ }
+ }
+ queueCompletion(priv, QIOOperation::Error::None);
+ }
+ m_operations.removeOne(op);
+ });
+ m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest)));
+
+ return op;
+}
+
+QIOVectoredWriteOperation *
+QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers)
+{
+ if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredWrite))
+ return nullptr;
+ auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers);
+
+ auto *priv = new QIOOperationPrivate(dataStorage);
+ priv->offset = offset;
+ priv->type = QIOOperation::Type::Write;
+
+ auto *op = new QIOVectoredWriteOperation(*priv, q_ptr);
+ if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
+ queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
+ return op;
+ }
+ m_operations.append(op);
+
+ QIORingRequest<QIORing::Operation::VectoredWrite> writeRequest;
+ writeRequest.fd = m_fd;
+ writeRequest.offset = priv->offset;
+ writeRequest.sources = dataStorage->getWriteSpans();
+ writeRequest.setCallback(
+ [this, op](const QIORingRequest<QIORing::Operation::VectoredWrite> &request) {
+ auto *priv = QIOOperationPrivate::get(op);
+ if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
+ if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
+ queueCompletion(priv, QIOOperation::Error::Aborted);
+ else
+ queueCompletion(priv, QIOOperation::Error::Write);
+ } else if (const auto *result = std::get_if<
+ QIORingResult<QIORing::Operation::VectoredWrite>>(
+ &request.result)) {
+ priv->appendBytesProcessed(result->bytesWritten);
+ queueCompletion(priv, QIOOperation::Error::None);
+ }
+ m_operations.removeOne(op);
+ });
+ m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest)));
+
+ return op;
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/tools/qlist.h b/src/corelib/tools/qlist.h
index a11f7913dc7..e69b9aebabb 100644
--- a/src/corelib/tools/qlist.h
+++ b/src/corelib/tools/qlist.h
@@ -301,21 +301,27 @@ public:
explicit QList(qsizetype size)
: d(size)
{
- if (size)
+ if (size) {
+ Q_CHECK_PTR(d.data());
d->appendInitialize(size);
+ }
}
QList(qsizetype size, parameter_type t)
: d(size)
{
- if (size)
+ if (size) {
+ Q_CHECK_PTR(d.data());
d->copyAppend(size, t);
+ }
}
inline QList(std::initializer_list<T> args)
: d(qsizetype(args.size()))
{
- if (args.size())
+ if (args.size()) {
+ Q_CHECK_PTR(d.data());
d->copyAppend(args.begin(), args.end());
+ }
}
QList<T> &operator=(std::initializer_list<T> args)
@@ -332,6 +338,7 @@ public:
const auto distance = std::distance(i1, i2);
if (distance) {
d = DataPointer(qsizetype(distance));
+ Q_CHECK_PTR(d.data());
// appendIteratorRange can deal with contiguous iterators on its own,
// this is an optimization for C++17 code.
if constexpr (std::is_same_v<std::decay_t<InputIterator>, iterator> ||
@@ -352,8 +359,10 @@ public:
QList(qsizetype size, Qt::Initialization)
: d(size)
{
- if (size)
+ if (size) {
+ Q_CHECK_PTR(d.data());
d->appendUninitialized(size);
+ }
}
// compiler-generated special member functions are fine!
@@ -823,7 +832,10 @@ void QList<T>::reserve(qsizetype asize)
}
}
- DataPointer detached(qMax(asize, size()));
+ qsizetype newSize = qMax(asize, size());
+ DataPointer detached(newSize);
+ if (newSize)
+ Q_CHECK_PTR(detached.data());
detached->copyAppend(d->begin(), d->end());
if (detached.d_ptr())
detached->setFlag(Data::CapacityReserved);
@@ -839,6 +851,7 @@ inline void QList<T>::squeeze()
// must allocate memory
DataPointer detached(size());
if (size()) {
+ Q_CHECK_PTR(detached.data());
if (d.needsDetach())
detached->copyAppend(d.data(), d.data() + d.size);
else
diff --git a/src/gui/math3d/qmatrix4x4.cpp b/src/gui/math3d/qmatrix4x4.cpp
index 147807cd293..f6a06fd47ca 100644
--- a/src/gui/math3d/qmatrix4x4.cpp
+++ b/src/gui/math3d/qmatrix4x4.cpp
@@ -241,7 +241,7 @@ QMatrix4x4::QMatrix4x4(const QTransform& transform)
/*!
\fn void QMatrix4x4::fill(float value)
- Fills all elements of this matrx with \a value.
+ Fills all elements of this matrix with \a value.
*/
static inline double matrixDet2(const double m[4][4], int col0, int col1, int row0, int row1)
diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt
index d9415f0a011..9a5db541a42 100644
--- a/src/plugins/platforms/wayland/CMakeLists.txt
+++ b/src/plugins/platforms/wayland/CMakeLists.txt
@@ -78,7 +78,6 @@ qt_internal_add_module(WaylandClient
qwaylandviewport.cpp qwaylandviewport_p.h
qwaylandwindow.cpp qwaylandwindow_p.h
qwaylandwindowmanagerintegration.cpp qwaylandwindowmanagerintegration_p.h
- qwaylandeventdispatcher.cpp qwaylandeventdispatcher_p.h
shellintegration/qwaylandclientshellapi_p.h
shellintegration/qwaylandshellintegration_p.h shellintegration/qwaylandshellintegration.cpp
shellintegration/qwaylandshellintegrationfactory.cpp shellintegration/qwaylandshellintegrationfactory_p.h
diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp
index 1fc5e5c30a0..f20b0c5a7dc 100644
--- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp
+++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp
@@ -32,7 +32,6 @@
#include "qwaylandtextinputv3_p.h"
#include "qwaylandinputcontext_p.h"
#include "qwaylandinputmethodcontext_p.h"
-#include "qwaylandeventdispatcher_p.h"
#include "qwaylandwindowmanagerintegration_p.h"
#include "qwaylandshellintegration_p.h"
@@ -525,7 +524,6 @@ void QWaylandDisplay::reconnect()
void QWaylandDisplay::flushRequests()
{
m_eventThread->readAndDispatchEvents();
- QWindowSystemInterface::flushWindowSystemEvents(QWaylandEventDispatcher::eventDispatcher->flags());
}
// We have to wait until we have an eventDispatcher before creating the eventThread,
diff --git a/src/plugins/platforms/wayland/qwaylandeventdispatcher.cpp b/src/plugins/platforms/wayland/qwaylandeventdispatcher.cpp
deleted file mode 100644
index f5f14f29824..00000000000
--- a/src/plugins/platforms/wayland/qwaylandeventdispatcher.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (C) 2025 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "qwaylandeventdispatcher_p.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace QtWaylandClient {
-
-QWaylandEventDispatcher *QWaylandEventDispatcher::eventDispatcher = nullptr;
-
-QAbstractEventDispatcher *QWaylandEventDispatcher::createEventDispatcher()
-{
-#if !defined(QT_NO_GLIB) && !defined(Q_OS_WIN)
- if (qEnvironmentVariableIsEmpty("QT_NO_GLIB") && QEventDispatcherGlib::versionSupported())
- return new QWaylandGlibEventDispatcher();
-#endif
- return new QWaylandUnixEventDispatcher();
-}
-
-QWaylandEventDispatcher::QWaylandEventDispatcher()
-{
- Q_ASSERT(!eventDispatcher);
- eventDispatcher = this;
-}
-
-QWaylandEventDispatcher::~QWaylandEventDispatcher()
-{
- Q_ASSERT(eventDispatcher == this);
- eventDispatcher = nullptr;
-}
-
-bool QWaylandUnixEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
-{
- m_flags = flags;
- return QUnixEventDispatcherQPA::processEvents(flags);
-}
-
-QEventLoop::ProcessEventsFlags QWaylandUnixEventDispatcher::flags() const
-{
- return m_flags;
-}
-
-#if !defined(QT_NO_GLIB) && !defined(Q_OS_WIN)
-
-QEventLoop::ProcessEventsFlags QWaylandGlibEventDispatcher::flags() const
-{
- return m_flags;
-}
-
-#endif
-
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wayland/qwaylandeventdispatcher_p.h b/src/plugins/platforms/wayland/qwaylandeventdispatcher_p.h
deleted file mode 100644
index a0426d44a21..00000000000
--- a/src/plugins/platforms/wayland/qwaylandeventdispatcher_p.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2025 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QWAYLANDEVENTDISPATCHER_P_H
-#define QWAYLANDEVENTDISPATCHER_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtGui/private/qunixeventdispatcher_qpa_p.h>
-#if !defined(QT_NO_GLIB) && !defined(Q_OS_WIN)
-#include <QtGui/private/qeventdispatcher_glib_p.h>
-#endif
-
-QT_BEGIN_NAMESPACE
-
-namespace QtWaylandClient {
-
-class QWaylandEventDispatcher
-{
-public:
- static QAbstractEventDispatcher *createEventDispatcher();
-
- static QWaylandEventDispatcher *eventDispatcher;
-
-public:
- QWaylandEventDispatcher();
- virtual ~QWaylandEventDispatcher();
-
- virtual QEventLoop::ProcessEventsFlags flags() const = 0;
-};
-
-class QWaylandUnixEventDispatcher : public QUnixEventDispatcherQPA, QWaylandEventDispatcher
-{
- Q_OBJECT
-public:
- bool processEvents(QEventLoop::ProcessEventsFlags flags) override;
-
- QEventLoop::ProcessEventsFlags flags() const override;
-
-private:
- QEventLoop::ProcessEventsFlags m_flags;
-};
-
-#if !defined(QT_NO_GLIB) && !defined(Q_OS_WIN)
-
-class QWaylandGlibEventDispatcher : public QPAEventDispatcherGlib, QWaylandEventDispatcher
-{
- Q_OBJECT
-public:
- QEventLoop::ProcessEventsFlags flags() const override;
-};
-
-#endif
-
-}
-
-QT_END_NAMESPACE
-
-#endif
diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp
index b16a97d4c77..28a0cca9e55 100644
--- a/src/plugins/platforms/wayland/qwaylandintegration.cpp
+++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp
@@ -18,7 +18,6 @@
#include "qwaylandplatformservices_p.h"
#include "qwaylandscreen_p.h"
#include "qwaylandcursor_p.h"
-#include "qwaylandeventdispatcher_p.h"
#if defined(Q_OS_MACOS)
# include <QtGui/private/qcoretextfontdatabase_p.h>
@@ -26,6 +25,7 @@
#else
# include <QtGui/private/qgenericunixfontdatabase_p.h>
#endif
+#include <QtGui/private/qgenericunixeventdispatcher_p.h>
#include <QtGui/private/qgenericunixtheme_p.h>
#include <QtGui/private/qguiapplication_p.h>
@@ -182,7 +182,7 @@ QPlatformBackingStore *QWaylandIntegration::createPlatformBackingStore(QWindow *
QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const
{
- return QWaylandEventDispatcher::createEventDispatcher();
+ return createUnixEventDispatcher();
}
QPlatformNativeInterface *QWaylandIntegration::createPlatformNativeInterface()
diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
index 50087615d7a..7faacca3d6c 100644
--- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
+++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
@@ -1301,7 +1301,7 @@ bool QMYSQLDriver::open(const QString &db,
{"MYSQL_OPT_SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString},
{"MYSQL_OPT_SSL_CRL"_L1, MYSQL_OPT_SSL_CRL, setOptionString},
{"MYSQL_OPT_SSL_CRLPATH"_L1, MYSQL_OPT_SSL_CRLPATH, setOptionString},
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50710
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50710 || defined(MARIADB_VERSION_ID)
{"MYSQL_OPT_TLS_VERSION"_L1, MYSQL_OPT_TLS_VERSION, setOptionString},
#endif
#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID)
diff --git a/src/sql/doc/src/sql-driver.qdoc b/src/sql/doc/src/sql-driver.qdoc
index 8fdf12f12da..98825b6ac3a 100644
--- a/src/sql/doc/src/sql-driver.qdoc
+++ b/src/sql/doc/src/sql-driver.qdoc
@@ -227,7 +227,7 @@
a combination of 'TLSv1' ,' TLSv1.1', 'TLSv1.2' or 'TLSv1.3' depending on the used \l
{https://dev.mysql.com/doc/refman/8.0/en/encrypted-connection-protocols-ciphers.html#encrypted-connection-protocol-configuration}
{MySQL server} version.
- Only available when linked against MySQL 5.7.11 or higher. Not available for MariaDB.
+ Only available when linked against MySQL 5.7.11 or higher or MariaDB C Connector 3.1.10.
\row
\li MYSQL_OPT_SSL_KEY / SSL_KEY (deprecated)
\li The path name of the client private key file
diff --git a/tests/auto/corelib/io/CMakeLists.txt b/tests/auto/corelib/io/CMakeLists.txt
index f76900162ca..c0d5ea3136e 100644
--- a/tests/auto/corelib/io/CMakeLists.txt
+++ b/tests/auto/corelib/io/CMakeLists.txt
@@ -9,6 +9,9 @@ endif()
if(QT_FEATURE_private_tests)
add_subdirectory(qabstractfileengine)
add_subdirectory(qfileinfo)
+ if(LINUX AND QT_FEATURE_liburing)
+ add_subdirectory(qioring)
+ endif()
add_subdirectory(qipaddress)
add_subdirectory(qloggingregistry)
if(QT_FEATURE_async_io)
diff --git a/tests/auto/corelib/io/qioring/CMakeLists.txt b/tests/auto/corelib/io/qioring/CMakeLists.txt
new file mode 100644
index 00000000000..19862c3fc17
--- /dev/null
+++ b/tests/auto/corelib/io/qioring/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qfilesystementry LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qioring
+ SOURCES
+ tst_qioring.cpp
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::TestPrivate
+ DEFINES
+ QTEST_THROW_ON_FAIL
+ QTEST_THROW_ON_SKIP
+)
diff --git a/tests/auto/corelib/io/qioring/data/input.txt b/tests/auto/corelib/io/qioring/data/input.txt
new file mode 100644
index 00000000000..34c92c2d93d
--- /dev/null
+++ b/tests/auto/corelib/io/qioring/data/input.txt
@@ -0,0 +1 @@
+lorem ipsum \ No newline at end of file
diff --git a/tests/auto/corelib/io/qioring/tst_qioring.cpp b/tests/auto/corelib/io/qioring/tst_qioring.cpp
new file mode 100644
index 00000000000..1128bcd7979
--- /dev/null
+++ b/tests/auto/corelib/io/qioring/tst_qioring.cpp
@@ -0,0 +1,263 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/qtest.h>
+
+#include <QtCore/private/qioring_p.h>
+
+#include <QtCore/private/qcore_unix_p.h>
+
+using namespace Qt::StringLiterals;
+using namespace std::chrono_literals;
+
+class tst_QIORing : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void construct();
+ void sharedInstance();
+ void open();
+ void read();
+ void write();
+ void stat();
+ void fiveGiBReadWrite();
+
+private:
+ static void closeFile(qintptr fd);
+ static qintptr openHelper(QIORing *ring, const QString &path, QIODevice::OpenMode flags);
+};
+
+void tst_QIORing::closeFile(qintptr fd)
+{
+ QT_CLOSE(fd);
+}
+
+qintptr tst_QIORing::openHelper(QIORing *ring, const QString &path, QIODevice::OpenMode flags)
+{
+ QIORingRequest<QIORing::Operation::Open> request;
+ request.path = QtPrivate::toFilesystemPath(path);
+ request.flags = flags;
+ qintptr fd = -1;
+ request.setCallback([&fd](const QIORingRequest<QIORing::Operation::Open> &request) {
+ const auto *result = std::get_if<QIORingResult<QIORing::Operation::Open>>(&request.result);
+ QVERIFY(result);
+ fd = result->fd;
+ });
+
+ QIORing::RequestHandle handle = ring->queueRequest(std::move(request));
+ ring->waitForRequest(handle, 500ms);
+ QCOMPARE_GE(fd, 0);
+ return fd;
+}
+
+void tst_QIORing::construct()
+{
+ QIORing ring(1, 2);
+ QVERIFY(ring.ensureInitialized());
+
+ // Everything must supports the basics:
+ QVERIFY(ring.supportsOperation(QIORing::Operation::Read));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::Write));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::Close));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::Open));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::Flush));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::Cancel));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::VectoredRead));
+ QVERIFY(ring.supportsOperation(QIORing::Operation::VectoredWrite));
+
+ QCOMPARE_GE(ring.submissionQueueSize(), 1u);
+ QCOMPARE_GE(ring.completionQueueSize(), 2u);
+}
+
+void tst_QIORing::sharedInstance()
+{
+ QIORing *shared = QIORing::sharedInstance();
+ QVERIFY(shared);
+ QCOMPARE_GE(shared->submissionQueueSize(), QIORing::DefaultSubmissionQueueSize);
+ QCOMPARE_GE(shared->completionQueueSize(), QIORing::DefaultCompletionQueueSize);
+}
+
+void tst_QIORing::open()
+{
+ QString sourceDir = QFINDTESTDATA("data");
+ QIORing ring;
+ QVERIFY(ring.ensureInitialized());
+
+ QIORingRequest<QIORing::Operation::Open> openRequest;
+ openRequest.path = QtPrivate::toFilesystemPath(sourceDir + "/input.txt"_L1);
+ openRequest.flags = QIODevice::ReadOnly | QIODevice::ExistingOnly;
+ qintptr fd = -1;
+ openRequest.setCallback([&fd](const QIORingRequest<QIORing::Operation::Open> &request) {
+ if (request.result.index() == 1) {
+ const auto &result = std::get<QIORingResult<QIORing::Operation::Open>>(request.result);
+ QCOMPARE_GE(result.fd, 0);
+ fd = result.fd;
+ } else {
+ const auto &error = std::get<QFileDevice::FileError>(request.result);
+ QFAIL(qPrintable("Failed to open file: %1"_L1.arg(QString::number(int(error)))));
+ }
+ });
+ QIORing::RequestHandle handle = ring.queueRequest(std::move(openRequest));
+ ring.waitForRequest(handle, 500ms);
+ QVERIFY(fd >= 0);
+ closeFile(fd);
+}
+
+void tst_QIORing::read()
+{
+ QFile file(QFINDTESTDATA("data/input.txt"));
+ QVERIFY(file.open(QIODevice::ReadOnly));
+ int fd = file.handle();
+ qintptr nativeFd = fd;
+
+ QIORing ring;
+ QVERIFY(ring.ensureInitialized());
+ QIORingRequest<QIORing::Operation::Read> readRequest;
+ readRequest.fd = nativeFd;
+ readRequest.offset = sizeof("lorem ") - 1;
+ std::array<std::byte, sizeof("ipsum") - 1> buffer{};
+ readRequest.destination = buffer;
+ qint64 bytesRead = 0;
+ readRequest.setCallback([&bytesRead](const QIORingRequest<QIORing::Operation::Read> &request) {
+ const auto *result = std::get_if<QIORingResult<QIORing::Operation::Read>>(&request.result);
+ QVERIFY(result);
+ bytesRead = result->bytesRead;
+ });
+ QIORing::RequestHandle handle = ring.queueRequest(std::move(readRequest));
+ QVERIFY(ring.waitForRequest(handle));
+ QCOMPARE(bytesRead, sizeof("ipsum") - 1);
+ QCOMPARE(QLatin1StringView(buffer), "ipsum");
+}
+
+void tst_QIORing::write()
+{
+ QIORing ring;
+ QVERIFY(ring.ensureInitialized());
+
+ QTemporaryDir dir;
+ auto path = dir.filePath("out");
+
+ auto fd = openHelper(&ring, path, QIODevice::ReadWrite);
+ auto cleanup = qScopeGuard([fd](){
+ closeFile(fd);
+ });
+
+ QIORingRequest<QIORing::Operation::Write> writeRequest;
+ writeRequest.fd = fd;
+ writeRequest.offset = 0;
+ QByteArray buffer(1024 * 1024 * 10, 'a');
+ QSpan span = buffer;
+ writeRequest.source = as_bytes(span);
+
+ qint64 bytesWritten = 0;
+ writeRequest.setCallback( //
+ [&bytesWritten](const QIORingRequest<QIORing::Operation::Write> &request) {
+ const auto *result = std::get_if<QIORingResult<QIORing::Operation::Write>>(
+ &request.result);
+ QVERIFY(result);
+ bytesWritten = result->bytesWritten;
+ });
+ QIORing::RequestHandle handle = ring.queueRequest(std::move(writeRequest));
+ QVERIFY(ring.waitForRequest(handle));
+ QCOMPARE(bytesWritten, buffer.size());
+
+ // And read back again:
+ QIORingRequest<QIORing::Operation::Read> readRequest;
+ readRequest.fd = fd;
+ readRequest.offset = 0;
+ std::fill(buffer.begin(), buffer.end(), '\0');
+ readRequest.destination = as_writable_bytes(span);
+
+ qint64 bytesRead = 0;
+ readRequest.setCallback([&bytesRead](const QIORingRequest<QIORing::Operation::Read> &request) {
+ const auto *result = std::get_if<QIORingResult<QIORing::Operation::Read>>(&request.result);
+ QVERIFY(result);
+ bytesRead = result->bytesRead;
+ });
+ handle = ring.queueRequest(std::move(readRequest));
+ QVERIFY(ring.waitForRequest(handle));
+ QCOMPARE(bytesRead, buffer.size());
+ QVERIFY(std::all_of(buffer.begin(), buffer.end(), [](char ch) { return ch == 'a'; }));
+}
+
+void tst_QIORing::stat()
+{
+ QIORing ring;
+ auto fd = openHelper(&ring, QFINDTESTDATA("data/input.txt"), QIODevice::ReadOnly);
+
+ QVERIFY(ring.ensureInitialized());
+ QIORingRequest<QIORing::Operation::Stat> statRequest;
+ statRequest.fd = fd;
+ quint64 size = 0;
+ statRequest.setCallback([&size](const QIORingRequest<QIORing::Operation::Stat> &request) {
+ const auto *result = std::get_if<QIORingResult<QIORing::Operation::Stat>>(&request.result);
+ QVERIFY(result);
+ size = result->size;
+ });
+ QIORing::RequestHandle handle = ring.queueRequest(std::move(statRequest));
+ QVERIFY(ring.waitForRequest(handle));
+ QCOMPARE(size, 11);
+}
+
+void tst_QIORing::fiveGiBReadWrite()
+{
+#if Q_PROCESSOR_WORDSIZE < 8
+ QSKIP("Can't test this on 32-bit.");
+#else
+ static constexpr qsizetype Size = 5ll * 1024 * 1024 * 1024;
+ std::unique_ptr<std::byte[]> bytes(new (std::nothrow) std::byte[Size]);
+ if (!bytes)
+ QSKIP("Failed to allocate the buffer (not enough memory?)");
+ std::fill_n(bytes.get(), Size, std::byte(242));
+
+ QIORing ring;
+ QVERIFY(ring.ensureInitialized());
+
+ QTemporaryDir dir;
+ auto path = dir.filePath("largefile");
+
+ auto fd = openHelper(&ring, path, QIODevice::ReadWrite);
+ auto cleanup = qScopeGuard([fd]() { closeFile(fd); });
+
+ QIORingRequest<QIORing::Operation::Write> writeRequest;
+ writeRequest.fd = fd;
+ writeRequest.offset = 0;
+ QSpan span = QSpan(bytes.get(), Size);
+ writeRequest.source = span;
+
+ quint64 bytesWritten = 0;
+ writeRequest.setCallback( //
+ [&bytesWritten](const QIORingRequest<QIORing::Operation::Write> &request) {
+ auto *result = std::get_if<QIORingResult<QIORing::Operation::Write>>(
+ &request.result);
+ QVERIFY(result);
+ bytesWritten = result->bytesWritten;
+ QCOMPARE(bytesWritten, Size);
+ });
+ QIORing::RequestHandle handle = ring.queueRequest(std::move(writeRequest));
+ QVERIFY(ring.waitForRequest(handle));
+
+ // And read back again:
+ QIORingRequest<QIORing::Operation::Read> readRequest;
+ readRequest.fd = fd;
+ readRequest.offset = 0;
+ std::fill_n(bytes.get(), Size, std::byte('\0'));
+ readRequest.destination = span;
+
+ quint64 bytesRead = 0;
+ readRequest.setCallback([&bytesRead](const QIORingRequest<QIORing::Operation::Read> &request) {
+ const auto *result = std::get_if<QIORingResult<QIORing::Operation::Read>>(&request.result);
+ QVERIFY(result);
+ bytesRead = result->bytesRead;
+ QCOMPARE(bytesRead, Size);
+ });
+ handle = ring.queueRequest(std::move(readRequest));
+ QVERIFY(ring.waitForRequest(handle));
+ QVERIFY(std::all_of(bytes.get(), bytes.get() + Size,
+ [](std::byte ch) { return ch == std::byte(242); }));
+#endif
+}
+
+QTEST_MAIN(tst_QIORing)
+#include <tst_qioring.moc>
diff --git a/tests/auto/corelib/io/qrandomaccessasyncfile/tst_qrandomaccessasyncfile.cpp b/tests/auto/corelib/io/qrandomaccessasyncfile/tst_qrandomaccessasyncfile.cpp
index 6dceb583469..bcc30f98700 100644
--- a/tests/auto/corelib/io/qrandomaccessasyncfile/tst_qrandomaccessasyncfile.cpp
+++ b/tests/auto/corelib/io/qrandomaccessasyncfile/tst_qrandomaccessasyncfile.cpp
@@ -78,8 +78,9 @@ void tst_QRandomAccessAsyncFile::initTestCase()
QVERIFY(m_file.open());
QByteArray data(FileSize, Qt::Uninitialized);
+ auto *ptr = data.data();
for (qsizetype i = 0; i < FileSize; ++i)
- data[i] = char(i % 256);
+ ptr[i] = char(i % 256);
qint64 written = m_file.write(data);
QCOMPARE_EQ(written, FileSize);
@@ -88,7 +89,13 @@ void tst_QRandomAccessAsyncFile::initTestCase()
void tst_QRandomAccessAsyncFile::cleanupTestCase()
{
m_file.close();
- QVERIFY(m_file.remove());
+ using namespace std::chrono_literals;
+ QDeadlineTimer dt(10s);
+ // Loop a little bit in case there is an access race on Windows:
+ bool success = false;
+ while (!(success = m_file.remove()) && !dt.hasExpired())
+ QThread::msleep(100);
+ QVERIFY2(success, qPrintable(m_file.errorString()));
}
void tst_QRandomAccessAsyncFile::size()
@@ -586,13 +593,16 @@ void tst_QRandomAccessAsyncFile::fileClosedInProgress()
}
file.close();
- auto isAbortedOrComplete = [](QIOOperation *op) {
- return op->error() == QIOOperation::Error::Aborted
- || op->error() == QIOOperation::Error::None;
+ auto isAbortedOrCompleteOrFailedToOpen = [](QIOOperation *op) {
+ return op->error() == QIOOperation::Error::Aborted // Aborted
+ || op->error() == QIOOperation::Error::Open // Failed, because other op is in progress
+ || op->error() == QIOOperation::Error::None; // Completed
};
for (auto op : operations) {
QTRY_VERIFY(op->isFinished());
- QVERIFY(isAbortedOrComplete(op));
+ QVERIFY2(isAbortedOrCompleteOrFailedToOpen(op),
+ qPrintable("Expected Aborted, Open or None, got %1"_L1.arg(
+ QDebug::toString(op->error()))));
}
}
diff --git a/tests/auto/corelib/tools/qlist/tst_qlist.cpp b/tests/auto/corelib/tools/qlist/tst_qlist.cpp
index b91665f55da..a93079b33de 100644
--- a/tests/auto/corelib/tools/qlist/tst_qlist.cpp
+++ b/tests/auto/corelib/tools/qlist/tst_qlist.cpp
@@ -301,6 +301,7 @@ private slots:
void constructors_emptyReserveZero() const;
void constructors_emptyReserve() const;
void constructors_reserveAndInitialize() const;
+ void constructorsThrowOnSillySize() const;
void copyConstructorInt() const { copyConstructor<int>(); }
void copyConstructorMovable() const { copyConstructor<Movable>(); }
void copyConstructorNoexceptMovable() const { copyConstructor<NoexceptMovable>(); }
@@ -704,6 +705,38 @@ void tst_QList::constructors_reserveAndInitialize() const
QCOMPARE(meaningoflife.i, 'n');
}
+void tst_QList::constructorsThrowOnSillySize() const
+{
+#ifdef QT_NO_EXCEPTIONS
+ QSKIP("Compiled without exception support");
+#else
+ // Only testing primitives for this; it should be enough.
+ using T = int;
+ QList<T> dummy(4, 1);
+
+ // This should cause QArrayData::allocate() to overflow and thus return
+ // nullptr.
+ constexpr size_t MaxMemory = std::numeric_limits<size_t>::max() / 4 * 3;
+ static_assert(MaxMemory > size_t(std::numeric_limits<ptrdiff_t>::max()));
+ static_assert(MaxMemory / sizeof(T) < size_t(std::numeric_limits<ptrdiff_t>::max() - 1));
+ constexpr qsizetype NumElements = MaxMemory / sizeof(T);
+
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l(NumElements));
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l(NumElements, 0));
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l(NumElements, Qt::Uninitialized));
+
+ // Since we're here, we might as well test resize() and reserve().
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l; l.reserve(NumElements));
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l; l.resize(NumElements));
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l; l.resize(NumElements, 0));
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l; l.resizeForOverwrite(NumElements));
+
+ // The reversed iterators will cause QList to pass a negative size to
+ // QArrayData::allocate(), which is also silly.
+ QVERIFY_THROWS_EXCEPTION(std::bad_alloc, QList<T> l(dummy.constEnd(), dummy.constBegin()));
+#endif
+}
+
template<typename T>
void tst_QList::copyConstructor() const
{