diff options
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, ¶ms); + 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 { |
