Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qioring_p.h
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#ifndef IOPROCESSOR_P_H
6#define IOPROCESSOR_P_H
7
8//
9// W A R N I N G
10// -------------
11//
12// This file is not part of the Qt API. It exists purely as an
13// implementation detail. This header file may change from version to
14// version without notice, or even be removed.
15//
16// We mean it.
17//
18
19#include <QtCore/private/qtcoreglobal_p.h>
20
21#include <QtCore/qstring.h>
22#include <QtCore/qspan.h>
23#include <QtCore/qhash.h>
24#include <QtCore/qfiledevice.h>
25#include <QtCore/qloggingcategory.h>
26#include <QtCore/qdeadlinetimer.h>
27
28#ifdef Q_OS_LINUX
29# include <QtCore/qsocketnotifier.h>
30struct io_uring_sqe;
31struct io_uring_cqe;
32#elif defined(Q_OS_WIN)
33# include <QtCore/qwineventnotifier.h>
34# include <qt_windows.h>
35# include <ioringapi.h>
36#endif
37
38#include <algorithm>
39#include <filesystem>
40#include <QtCore/qxpfunctional.h>
41#include <variant>
42#include <optional>
43#include <type_traits>
44
45/*
46 This file defines an interface for the backend of QRandomAccessFile.
47 The backends themselves are implemented in platform-specific files, such as
48 ioring_linux.cpp, ioring_win.cpp, etc.
49 And has a lower-level interface than the public interface will have, but the
50 separation hopefully makes it easier to implement the ioring backends, test
51 them, and tweak them without the higher-level interface needing to see
52 changes, and to make it possible to tweak the higher-level interface without
53 needing to touch the (somewhat similar) ioring backends.
54
55 Most of the interface is just an enum QIORing::Operation + the
56 QIORingRequest template class, which is specialized for each operation so it
57 carries just the relevant data for that operation. And a small mechanism to
58 store the request in a generic manner so they can be used in the
59 implementation files at the cost of some overhead.
60
61 There will be absolutely zero binary compatibility guarantees for this
62 interface.
63*/
64
66
68
69namespace QtPrivate {
71
72#define FOREACH_IO_OPERATION(OP)
73 OP(Open)
74 OP(Close)
75 OP(Read)
76 OP(Write)
77 OP(VectoredRead)
78 OP(VectoredWrite)
79 OP(Flush)
80 OP(Stat)
81 OP(Cancel)
82 /**/
83#define DEFINE_ENTRY(OP) OP,
84
85// clang-format off
91// clang-format on
93#undef DEFINE_ENTRY
94
95#ifdef Q_OS_WIN
96struct IORingApiTable;
97#endif
98
99#ifdef Q_OS_LINUX
101#endif
102}; // namespace QtPrivate
103
104template <QtPrivate::Operation Op>
106
107class QIORing final
108{
109 class GenericRequestType;
110 struct RequestHandleTag; // Just used as an opaque pointer
111public:
112 static constexpr quint32 DefaultSubmissionQueueSize = 128;
113 static constexpr quint32 DefaultCompletionQueueSize = DefaultSubmissionQueueSize * 2;
115 using RequestHandle = RequestHandleTag *;
116
117 Q_CORE_EXPORT
118 explicit QIORing(quint32 submissionQueueSize = DefaultSubmissionQueueSize,
119 quint32 completionQueueSize = DefaultCompletionQueueSize);
121 ~QIORing();
122 Q_DISABLE_COPY_MOVE(QIORing)
123
124 Q_CORE_EXPORT
125 static QIORing *sharedInstance();
126 bool ensureInitialized() { return initializeIORing(); }
127
129 static bool supportsOperation(Operation op);
130 template <Operation Op>
132 {
133 Q_ASSERT(supportsOperation(Op));
134 auto &r = pendingRequests.emplace_back(std::move(request));
135 addrItMap.emplace(&r, std::prev(pendingRequests.end()));
136 if (queueRequestInternal(r) == QueuedRequestStatus::CompletedImmediately)
137 return nullptr; // Return an invalid handle, to avoid ABA with following requests
138 return reinterpret_cast<RequestHandle>(&r);
139 }
141 void submitRequests();
143 bool waitForRequest(RequestHandle handle, QDeadlineTimer deadline = QDeadlineTimer::Forever);
144
145 quint32 submissionQueueSize() const noexcept { return sqEntries; }
146 quint32 completionQueueSize() const noexcept { return cqEntries; }
147
148private:
149 std::list<GenericRequestType> pendingRequests;
150 using PendingRequestsIterator = decltype(pendingRequests.begin());
151 QHash<void *, PendingRequestsIterator> addrItMap;
152 std::optional<PendingRequestsIterator> lastUnqueuedIterator;
153 quint32 sqEntries = 0;
154 quint32 cqEntries = 0;
155 quint32 inFlightRequests = 0;
156 quint32 unstagedRequests = 0;
157 bool stagePending = false;
158 bool preparingRequests = false;
159 qsizetype ongoingSplitOperations = 0;
160
161 Q_CORE_EXPORT
162 bool initializeIORing();
163
164 enum class QueuedRequestStatus : bool {
165 Pending = false,
166 CompletedImmediately = true,
167 };
168 Q_CORE_EXPORT
169 QueuedRequestStatus queueRequestInternal(GenericRequestType &request);
170 void prepareRequests();
171 void completionReady();
172 bool waitForCompletions(QDeadlineTimer deadline);
173
174 template <typename Fun>
175 static auto invokeOnOp(GenericRequestType &req, Fun fn);
176
177 template <Operation Op>
178 static void setFileErrorResult(QIORingRequest<Op> &req, QFileDevice::FileError error)
179 {
180 req.result.template emplace<QFileDevice::FileError>(error);
181 }
182 static void setFileErrorResult(GenericRequestType &req, QFileDevice::FileError error);
183 static void finishRequestWithError(GenericRequestType &req, QFileDevice::FileError error);
184 static bool verifyFd(GenericRequestType &req);
185
186 enum RequestPrepResult : quint8 {
187 Ok,
188 QueueFull,
189 Defer,
190 RequestCompleted,
191 };
192 enum class ReadWriteStatus : bool {
193 MoreToDo,
194 Finished,
195 };
196#ifdef Q_OS_LINUX
197 using NativeResultType = qint32;
198 static constexpr bool isResultFailure(NativeResultType result) { return result < 0; }
199
200 std::optional<QSocketNotifier> notifier;
201 // io_uring 'sq', 'sqe', and 'cqe' pointers:
202 void *submissionQueue = nullptr;
203 io_uring_sqe *submissionQueueEntries = nullptr;
204 const io_uring_cqe *completionQueueEntries = nullptr;
205
206 // Some pointers for working with the ring-buffer.
207 // The pointers to const are controlled by the kernel.
208 const quint32 *sqHead = nullptr;
209 quint32 *sqTail = nullptr;
210 const quint32 *sqIndexMask = nullptr;
211 quint32 *sqIndexArray = nullptr;
212 quint32 *cqHead = nullptr;
213 const quint32 *cqTail = nullptr;
214 const quint32 *cqIndexMask = nullptr;
215 // Because we want the flush to act as a barrier operation we need to track
216 // if there is one currently in progress. With kernel 6.16+ this seems to be
217 // fixed, but since we support older kernels we implement this deferring
218 // ourselves.
219 bool flushInProgress = false;
220
221 int io_uringFd = -1;
222 int eventDescriptor = -1;
223 [[nodiscard]]
224 RequestPrepResult prepareRequest(io_uring_sqe *sqe, GenericRequestType &request);
225
226 template <typename SpanOfBytes>
227 auto getVectoredOpAddressAndSize(QIORing::GenericRequestType &request,
228 QSpan<SpanOfBytes> spans);
229#elif defined(Q_OS_WIN)
230 // We use UINT32 because that's the type used for size parameters in their API.
231 static constexpr qsizetype MaxReadWriteLen = std::numeric_limits<UINT32>::max();
232 using NativeResultType = HRESULT;
233 static constexpr bool isResultFailure(NativeResultType result)
234 {
235 return FAILED(result) && result != HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
236 }
237
238 std::optional<QWinEventNotifier> notifier;
239 HIORING ioRingHandle = nullptr;
240 HANDLE eventHandle = INVALID_HANDLE_VALUE;
241 const QtPrivate::IORingApiTable *apiTable;
242
243 bool initialized = false;
244 bool queueWasFull = false;
245
246 [[nodiscard]]
247 RequestPrepResult prepareRequest(GenericRequestType &request);
248#endif
249 static QFileDevice::FileError mapFileError(NativeResultType result,
250 QFileDevice::FileError defaultValue);
251 using SetResultFn = qxp::function_ref<qint64(qint64)>;
252 static ReadWriteStatus handleReadCompletion(size_t value, QSpan<std::byte> *destinations,
253 void *voidExtra, SetResultFn setResult);
254 template <Operation Op>
255 ReadWriteStatus handleReadCompletion(NativeResultType result, size_t value,
256 GenericRequestType *request);
257 static ReadWriteStatus handleWriteCompletion(size_t value,
258 const QSpan<const std::byte> *sources,
259 void *voidExtra, SetResultFn setResult);
260 template <Operation Op>
261 ReadWriteStatus handleWriteCompletion(NativeResultType result, size_t value,
262 GenericRequestType *request);
263 void finalizeReadWriteCompletion(GenericRequestType *request, ReadWriteStatus rwstatus);
264};
265
267{
268};
269
270template <QtPrivate::Operation Op>
272template <QtPrivate::Operation Op>
273struct QIORingRequest;
274
275// @todo: q23::expected once emplace() returns a reference
276template <QtPrivate::Operation Op>
277using ExpectedResultType = std::variant<std::monostate, QIORingResult<Op>, QFileDevice::FileError>;
278
284
285template <QtPrivate::Operation Op, typename Base = QIORingRequestOffsetFdBase>
287{
288 ExpectedResultType<Op> result; // To be filled in by the backend
290 template <typename Func>
291 Q_ALWAYS_INLINE void setCallback(Func &&func)
292 {
293 using Prototype = void (*)(const QIORingRequest<Op> &);
294 callback.reset(QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(func)));
295 }
296};
297
298template <>
300{
301 // On Windows this is a HANDLE
303};
304template <>
305struct QIORingRequest<QtPrivate::Operation::Open> final
306 : QIORingRequestBase<QtPrivate::Operation::Open, QIORingRequestEmptyBase>
307{
308 std::filesystem::path path;
310};
311template <>
313{
314};
315template <>
316struct QIORingRequest<QtPrivate::Operation::Close> final
317 : QIORingRequestBase<QtPrivate::Operation::Close, QIORingRequestEmptyBase>
318{
319 // On Windows this is a HANDLE
321};
322
323template <>
328template <>
329struct QIORingRequest<QtPrivate::Operation::Write> final
330 : QIORingRequestBase<QtPrivate::Operation::Write>
331{
333};
334template <>
335struct QIORingResult<QtPrivate::Operation::VectoredWrite> final
337{
338};
339template <>
340struct QIORingRequest<QtPrivate::Operation::VectoredWrite> final
341 : QIORingRequestBase<QtPrivate::Operation::VectoredWrite>
342{
343 QSpan<const QSpan<const std::byte>> sources;
344};
345
346template <>
351template <>
352struct QIORingRequest<QtPrivate::Operation::Read> final
353 : QIORingRequestBase<QtPrivate::Operation::Read>
354{
356};
357
358template <>
359struct QIORingResult<QtPrivate::Operation::VectoredRead> final
361{
362};
363template <>
364struct QIORingRequest<QtPrivate::Operation::VectoredRead> final
365 : QIORingRequestBase<QtPrivate::Operation::VectoredRead>
366{
368};
369
370template <>
371struct QIORingResult<QtPrivate::Operation::Flush> final
372{
373 // No value in the result, just a success or failure
374};
375template <>
376struct QIORingRequest<QtPrivate::Operation::Flush> final : QIORingRequestBase<QtPrivate::Operation::Flush, QIORingRequestEmptyBase>
377{
378 // On Windows this is a HANDLE
380};
381
382template <>
383struct QIORingResult<QtPrivate::Operation::Stat> final
384{
386};
387template <>
388struct QIORingRequest<QtPrivate::Operation::Stat> final
389 : QIORingRequestBase<QtPrivate::Operation::Stat, QIORingRequestEmptyBase>
390{
391 // On Windows this is a HANDLE
393};
394
395// This is not inheriting the QIORingRequestBase because it doesn't have a result,
396// whether it was successful or not is indicated by whether the operation
397// it was cancelling was successful or not.
398template <>
399struct QIORingRequest<QtPrivate::Operation::Cancel> final : QIORingRequestEmptyBase
400{
403 template <typename Func>
404 Q_ALWAYS_INLINE void setCallback(Func &&func)
405 {
406 using Op = QtPrivate::Operation;
407 using Prototype = void (*)(const QIORingRequest<Op::Cancel> &);
408 callback.reset(QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(func)));
409 }
410};
411
412template <QIORing::Operation Op>
414{
415 if (!request.callback)
416 return;
417 void *args[2] = { nullptr, const_cast<QIORingRequest<Op> *>(&request) };
418 request.callback->call(nullptr, args);
419}
420
421class QIORing::GenericRequestType
422{
423 friend class QIORing;
424
425#define POPULATE_VARIANT(Op)
426 QIORingRequest<Operation::Op>,
427 /**/
428
429 std::variant<
430 FOREACH_IO_OPERATION(POPULATE_VARIANT)
431 std::monostate
432 > taggedUnion;
433
434#undef POPULATE_VARIANT
435
436 void *extraData = nullptr;
437 bool queued = false;
438
439 template <Operation Op>
440 Q_ALWAYS_INLINE void initializeStorage(QIORingRequest<Op> &&t) noexcept
441 {
442 static_assert(Op < Operation::NumOperations);
443 taggedUnion.emplace<QIORingRequest<Op>>(std::move(t));
444 }
445
446 Q_CORE_EXPORT
447 static void cleanupExtra(Operation op, void *extra);
448 template <typename T>
449 T *getOrInitializeExtra()
450 {
451 if (!extraData)
452 extraData = new T();
453 return static_cast<T *>(extraData);
454 }
455 template <typename T>
456 T *getExtra() const
457 {
458 return static_cast<T *>(extraData);
459 }
460 void reset() noexcept
461 {
462 Operation op = operation();
463 taggedUnion.emplace<std::monostate>();
464 if (extraData)
465 cleanupExtra(op, std::exchange(extraData, nullptr));
466 }
467
468public:
469 template <Operation Op>
470 explicit GenericRequestType(QIORingRequest<Op> &&t) noexcept
471 {
472 initializeStorage(std::move(t));
473 }
475 {
476 reset();
477 }
479 // We have to provide equality operators. Since copying is disabled, we just check for equality
480 // based on the address in memory. Two requests could be constructed to be equal, but we don't
481 // actually care because the order in which they are added to the queue may also matter.
482 friend bool operator==(const GenericRequestType &l, const GenericRequestType &r) noexcept
483 {
484 return std::addressof(l) == std::addressof(r);
485 }
486 friend bool operator!=(const GenericRequestType &l, const GenericRequestType &r) noexcept
487 {
488 return !(l == r);
489 }
490
492 template <Operation Op>
494 {
495 if (operation() == Op)
497 Q_ASSERT("Wrong operation requested, see operation()");
498 return nullptr;
499 }
500 template <Operation Op>
502 {
503 if (operation() == Op)
505 Q_ASSERT("Wrong operation requested, see operation()");
506 return {};
507 }
508 bool wasQueued() const { return queued; }
509 void setQueued(bool status) { queued = status; }
510};
511
512template <typename Fun>
513auto QIORing::invokeOnOp(GenericRequestType &req, Fun fn)
514{
515#define INVOKE_ON_OP(Op) case
516 QIORing::Operation::Op:
517 fn(req.template requestData<Operation::Op>());
518 return;
519 /**/
520
521 switch (req.operation()) {
522 FOREACH_IO_OPERATION(INVOKE_ON_OP)
523 case QIORing::Operation::NumOperations:
524 break;
525 }
526
527 Q_UNREACHABLE();
528#undef INVOKE_ON_OP
529}
530
531template <QIORing::Operation Op>
532QIORing::ReadWriteStatus QIORing::handleReadCompletion(NativeResultType result, size_t value,
533 GenericRequestType *request)
534{
535 static_assert(Op == Operation::Read || Op == Operation::VectoredRead);
536 QIORingRequest<Op> *readRequest = request->requestData<Op>();
537 Q_ASSERT(readRequest);
538
539 if (isResultFailure(result)) { // error
540 QFileDevice::FileError fileError = mapFileError(result, QFileDevice::ReadError);
541 QIORing::setFileErrorResult(*readRequest, fileError);
542 return ReadWriteStatus::Finished;
543 }
544
545 auto setResult = [readRequest](qint64 bytesRead) {
546 auto &readResult = [&readRequest]() -> QIORingResult<Op> & {
547 if (auto *result = std::get_if<QIORingResult<Op>>(&readRequest->result))
548 return *result;
549 return readRequest->result.template emplace<QIORingResult<Op>>();
550 }();
551 readResult.bytesRead += bytesRead;
552 return readResult.bytesRead;
553 };
554
555 auto *destinations = [&readRequest]() {
556 if constexpr (Op == Operation::Read)
557 return &readRequest->destination;
558 else
559 return &readRequest->destinations[0];
560 }();
561
562 QIORing::ReadWriteStatus rwstatus = handleReadCompletion(value, destinations,
563 request->getExtra<void>(), setResult);
564 finalizeReadWriteCompletion(request, rwstatus);
565 return rwstatus;
566}
567
568template <QIORing::Operation Op>
569QIORing::ReadWriteStatus QIORing::handleWriteCompletion(NativeResultType result, size_t value,
570 GenericRequestType *request)
571{
572 static_assert(Op == Operation::Write || Op == Operation::VectoredWrite);
573 QIORingRequest<Op> *writeRequest = request->requestData<Op>();
574 Q_ASSERT(writeRequest);
575
576 if (isResultFailure(result)) { // error
577 QFileDevice::FileError fileError = mapFileError(result, QFileDevice::WriteError);
578 QIORing::setFileErrorResult(*writeRequest, fileError);
579 return ReadWriteStatus::Finished;
580 }
581
582 auto setResult = [writeRequest](qint64 bytesWritten) {
583 auto &writeResult = [&writeRequest]() -> QIORingResult<Op> & {
584 if (auto *result = std::get_if<QIORingResult<Op>>(&writeRequest->result))
585 return *result;
586 return writeRequest->result.template emplace<QIORingResult<Op>>();
587 }();
588 writeResult.bytesWritten += bytesWritten;
589 return writeResult.bytesWritten;
590 };
591 auto *sources = [&writeRequest]() {
592 if constexpr (Op == Operation::Write)
593 return &writeRequest->source;
594 else
595 return &writeRequest->sources[0];
596 }();
597 QIORing::ReadWriteStatus rwstatus = handleWriteCompletion(value, sources,
598 request->getExtra<void>(), setResult);
599 finalizeReadWriteCompletion(request, rwstatus);
600 return rwstatus;
601}
602
603namespace QtPrivate {
604// The 'extra' struct for Read/Write operations that must be split up
612} // namespace QtPrivate
613
614QT_END_NAMESPACE
615
616#endif // IOPROCESSOR_P_H
GenericRequestType(QIORingRequest< Op > &&t) noexcept
Definition qioring_p.h:470
Q_CORE_EXPORT void submitRequests()
RequestHandleTag * RequestHandle
Definition qioring_p.h:115
static Q_CORE_EXPORT bool supportsOperation(Operation op)
Q_CORE_EXPORT ~QIORing()
bool ensureInitialized()
Definition qioring_p.h:126
static constexpr quint32 DefaultSubmissionQueueSize
Definition qioring_p.h:112
static constexpr quint32 DefaultCompletionQueueSize
Definition qioring_p.h:113
quint32 completionQueueSize() const noexcept
Definition qioring_p.h:146
quint32 submissionQueueSize() const noexcept
Definition qioring_p.h:145
QIORing::RequestHandle queueRequest(QIORingRequest< Op > &&request)
Definition qioring_p.h:131
Q_CORE_EXPORT bool waitForRequest(RequestHandle handle, QDeadlineTimer deadline=QDeadlineTimer::Forever)
Definition qioring.cpp:70
QtPrivate::Operation Operation
Definition qioring_p.h:114
Combined button and popup list for selecting options.
Q_ENUM_NS(Operation)
decltype(std::declval< const T & >().result) DetectResult
Definition qioring.cpp:87
constexpr bool HasResultMember
Definition qioring.cpp:90
Q_ALWAYS_INLINE void invokeCallback(const QIORingRequest< Op > &request)
Definition qioring_p.h:413
#define FOREACH_IO_OPERATION(OP)
Definition qioring_p.h:72
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)
ExpectedResultType< Op > result
Definition qioring_p.h:288
Q_ALWAYS_INLINE void setCallback(Func &&func)
Definition qioring_p.h:291
QtPrivate::SlotObjUniquePtr callback
Definition qioring_p.h:289
Q_ALWAYS_INLINE void setCallback(Func &&func)
Definition qioring_p.h:404
QSpan< const QSpan< const std::byte > > sources
Definition qioring_p.h:343