9#include <QtCore/qcompilerdetection.h>
10#include <QtCore/qobject.h>
11#include <QtCore/qscopedvaluerollback.h>
13#include <qt_windows.h>
16#include <QtCore/q26numeric.h>
22static_assert(
sizeof(qsizetype) >
sizeof(UINT32),
23 "This code is written with assuming 64-bit Windows.");
25using namespace Qt::StringLiterals;
28#define FOREACH_WIN_IORING_FUNCTION(Fn)
29 Fn(BuildIoRingReadFile)
30 Fn(BuildIoRingWriteFile)
31 Fn(BuildIoRingFlushFile)
32 Fn(BuildIoRingCancelRequest)
33 Fn(QueryIoRingCapabilities)
38 Fn(PopIoRingCompletion)
39 Fn(SetIoRingCompletionEvent)
43#if QT_CONFIG(windows_ioring_runtime)
44# define DefineIORingFunction(Name)
45 using Name##Fn = decltype(&::Name);
46 Name##Fn Name = nullptr;
48# define DefineIORingFunction(Name)
49 using Name##Fn = decltype(&::Name);
50 Name##Fn Name = &::Name;
55#undef DefineIORingFunction
60#if !QT_CONFIG(windows_ioring_runtime)
61 Q_CONSTINIT
static const IORingApiTable apiTable;
63 constexpr bool success =
true;
67 const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
68 if (Q_UNLIKELY(!kernel32))
71# define ResolveFunction(Name)
72 apiTable.Name = IORingApiTable::Name##Fn(QFunctionPointer(GetProcAddress(kernel32, #Name)));
76# undef ResolveFunction
80# define TEST_TABLE_OK(X) apiTable.X &&
81# define BOOL_CHAIN(...) (__VA_ARGS__ true)
87 return success ?
std::addressof(apiTable) :
nullptr;
92 quint64 offset, quintptr userData)
95 const IORING_HANDLE_REF fileRef((HANDLE(fd)));
96 const IORING_BUFFER_REF bufferRef(destination.data());
97 const auto maxSize = q26::saturate_cast<UINT32>(destination.size());
98 Q_ASSERT(maxSize == destination.size());
101 return apiTable->BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
102 userData, IOSQE_FLAGS_NONE);
106 quint64 offset, quintptr userData)
109 const IORING_HANDLE_REF fileRef((HANDLE(fd)));
110 const IORING_BUFFER_REF bufferRef(
const_cast<
std::byte *>(source.data()));
111 const auto maxSize = q26::saturate_cast<UINT32>(source.size());
112 Q_ASSERT(maxSize == source.size());
116 return apiTable->BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
117 FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE);
123 CloseHandle(eventHandle);
124 apiTable->CloseIoRing(ioRingHandle);
128bool QIORing::initializeIORing()
133 if (apiTable = QtPrivate::getApiTable(); !apiTable) {
134 qCWarning(lcQIORing,
"Failed to retrieve API table");
138 IORING_CAPABILITIES capabilities;
139 apiTable->QueryIoRingCapabilities(&capabilities);
140 if (capabilities.MaxVersion < IORING_VERSION_3)
142 if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0)
145 qCDebug(lcQIORing) <<
"Creating QIORing, requesting space for" << sqEntries
146 <<
"submission queue entries, and" << cqEntries
147 <<
"completion queue entries";
149 IORING_CREATE_FLAGS flags;
150 memset(&flags, 0,
sizeof(flags));
151#if !defined(QT_DEBUG) && QT_CONFIG(windows_ioring_skip_builder_param_checks)
152 flags.Advisory |= IORING_CREATE_SKIP_BUILDER_PARAM_CHECKS;
154 HRESULT hr = apiTable->CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries,
157 qErrnoWarning(hr,
"failed to initialize QIORing");
160 auto earlyExitCleanup = qScopeGuard([
this]() {
161 if (eventHandle != INVALID_HANDLE_VALUE)
162 CloseHandle(eventHandle);
163 apiTable->CloseIoRing(ioRingHandle);
165 eventHandle = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
166 if (eventHandle == INVALID_HANDLE_VALUE) {
167 qErrnoWarning(
"Failed to create event handle");
170 notifier.emplace(eventHandle);
171 hr = apiTable->SetIoRingCompletionEvent(ioRingHandle, eventHandle);
173 qErrnoWarning(hr,
"Failed to assign the event handle to QIORing");
177 if (SUCCEEDED(apiTable->GetIoRingInfo(ioRingHandle, &info))) {
178 sqEntries = info.SubmissionQueueSize;
179 cqEntries = info.CompletionQueueSize;
180 qCDebug(lcQIORing) <<
"QIORing configured with capacity for" << sqEntries
181 <<
"submissions, and" << cqEntries <<
"completions.";
183 QObject::connect(std::addressof(*notifier), &QWinEventNotifier::activated,
184 std::addressof(*notifier), [
this]() { completionReady(); });
186 earlyExitCleanup.dismiss();
190QFileDevice::FileError QIORing::mapFileError(NativeResultType error,
191 QFileDevice::FileError defaultValue)
193 Q_ASSERT(FAILED(error));
194 if (error == E_ABORT)
195 return QFileDevice::AbortError;
199void QIORing::completionReady()
201 ResetEvent(eventHandle);
203 while (apiTable->PopIoRingCompletion(ioRingHandle, &entry) == S_OK) {
206 if (!addrItMap.contains(request)) {
207 qCDebug(lcQIORing) <<
"Got completed entry, but cannot find it in the map. Likely "
208 "deleted, ignoring. UserData pointer:"
212 qCDebug(lcQIORing) <<
"Got completed entry. Operation:" << request->operation()
213 <<
"- UserData pointer:" << request
214 <<
"- Result:" << qt_error_string(entry.ResultCode) <<
'('
215 << QByteArray(
"0x"_ba + QByteArray::number(entry.ResultCode, 16)).data()
217 switch (request->operation()) {
219 Q_UNREACHABLE_RETURN();
221 auto closeRequest = request->takeRequestData<Operation::Close>();
224 if (CloseHandle(HANDLE(closeRequest.fd)))
227 closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
228 invokeCallback(closeRequest);
232 const ReadWriteStatus status = handleReadCompletion<
Operation::Read>(
233 entry.ResultCode, entry.Information, request);
234 if (status == ReadWriteStatus::MoreToDo)
236 auto readRequest = request->takeRequestData<Operation::Read>();
237 invokeCallback(readRequest);
241 const ReadWriteStatus status = handleWriteCompletion<
Operation::Write>(
242 entry.ResultCode, entry.Information, request);
243 if (status == ReadWriteStatus::MoreToDo)
245 auto writeRequest = request->takeRequestData<Operation::Write>();
246 invokeCallback(writeRequest);
250 const ReadWriteStatus status = handleReadCompletion<
Operation::VectoredRead>(
251 entry.ResultCode, entry.Information, request);
252 if (status == ReadWriteStatus::MoreToDo)
254 auto vectoredReadRequest = request->takeRequestData<Operation::VectoredRead>();
255 invokeCallback(vectoredReadRequest);
259 const ReadWriteStatus status = handleWriteCompletion<
Operation::VectoredWrite>(
260 entry.ResultCode, entry.Information, request);
261 if (status == ReadWriteStatus::MoreToDo)
263 auto vectoredWriteRequest = request->takeRequestData<Operation::VectoredWrite>();
264 invokeCallback(vectoredWriteRequest);
268 auto flushRequest = request->takeRequestData<Operation::Flush>();
269 if (FAILED(entry.ResultCode)) {
270 qErrnoWarning(entry.ResultCode,
"Flush operation failed");
272 flushRequest.result.emplace<QFileDevice::FileError>(
273 QFileDevice::FileError::WriteError);
277 invokeCallback(flushRequest);
281 auto cancelRequest = request->takeRequestData<Operation::Cancel>();
282 invokeCallback(cancelRequest);
286 Q_UNREACHABLE_RETURN();
289 Q_UNREACHABLE_RETURN();
292 auto it = addrItMap.take(request);
293 pendingRequests.erase(it);
295 queueWasFull =
false;
298 if (unstagedRequests > 0)
302bool QIORing::waitForCompletions(QDeadlineTimer deadline)
304 notifier->setEnabled(
false);
305 auto reactivateNotifier = qScopeGuard([
this]() {
306 notifier->setEnabled(
true);
309 while (!deadline.hasExpired()) {
311 if (deadline.isForever()) {
314 timeout = q26::saturate_cast<DWORD>(deadline.remainingTime());
315 if (timeout == INFINITE)
318 if (WaitForSingleObject(eventHandle, timeout) == WAIT_OBJECT_0)
327 if (openRequest.flags.testFlag(QIODevice::ReadOnly))
328 access |= GENERIC_READ;
329 if (openRequest.flags.testFlag(QIODevice::WriteOnly))
330 access |= GENERIC_WRITE;
332 DWORD disposition = 0;
333 if (openRequest.flags.testFlag(QIODevice::Append)) {
334 qCWarning(lcQIORing,
"Opening file with Append not supported for random access file");
335 return INVALID_HANDLE_VALUE;
337 if (openRequest.flags.testFlag(QIODevice::NewOnly)) {
338 disposition = CREATE_NEW;
342 disposition = openRequest.flags.testFlag(QIODeviceBase::WriteOnly)
343 && !openRequest.flags.testFlags(QIODeviceBase::ExistingOnly)
347 const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
348 const DWORD flagsAndAttribs = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
349 HANDLE h = CreateFile(openRequest.path.native().c_str(), access, shareMode,
nullptr,
350 disposition, flagsAndAttribs,
nullptr);
351 if (h != INVALID_HANDLE_VALUE && openRequest.flags.testFlag(QIODeviceBase::Truncate)) {
352 FILE_END_OF_FILE_INFO info;
353 memset(&info, 0,
sizeof(info));
354 SetFileInformationByHandle(h, FileEndOfFileInfo, &info,
sizeof(info));
359bool QIORing::supportsOperation(
Operation op)
380 stagePending =
false;
381 if (unstagedRequests == 0)
386 const bool shouldTryWait = std::exchange(queueWasFull,
false);
387 const auto submitToRing = [
this, &shouldTryWait] {
388 quint32 submittedEntries = 0;
389 HRESULT hr = apiTable->SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1,
391 qCDebug(lcQIORing) <<
"Submitted" << submittedEntries <<
"requests";
392 unstagedRequests -= submittedEntries;
398 return submittedEntries > 0;
400 if (submitToRing() && shouldTryWait) {
403 if (unstagedRequests > 0)
408void QIORing::prepareRequests()
410 if (!lastUnqueuedIterator)
412 Q_ASSERT(!preparingRequests);
413 QScopedValueRollback<
bool> prepareGuard(preparingRequests,
true);
415 auto it = *lastUnqueuedIterator;
416 lastUnqueuedIterator.reset();
417 const auto end = pendingRequests.end();
418 while (!queueWasFull && it != end) {
420 switch (prepareRequest(request)) {
421 case RequestPrepResult::Ok:
425 case RequestPrepResult::QueueFull:
426 qCDebug(lcQIORing) <<
"Queue was reported as full, in flight requests:"
427 << inFlightRequests <<
"submission queue size:" << sqEntries
428 <<
"completion queue size:" << cqEntries;
430 lastUnqueuedIterator = it;
432 case RequestPrepResult::Defer:
433 qCDebug(lcQIORing) <<
"Request for" << request.operation()
434 <<
"had to be deferred, will not queue any more requests at the "
436 lastUnqueuedIterator = it;
438 case RequestPrepResult::RequestCompleted:
440 qCDebug(lcQIORing) <<
"Request for" << request.operation()
441 <<
"completed synchronously.";
442 addrItMap.remove(&request);
443 it = pendingRequests.erase(it);
448 if (it != pendingRequests.end())
449 lastUnqueuedIterator = it;
462 qCDebug(lcQIORing) <<
"Preparing a request with operation" << request.operation();
465 if (!verifyFd(request)) {
466 finishRequestWithError(request, QFileDevice::OpenError);
467 return RequestPrepResult::RequestCompleted;
470 switch (request.operation()) {
473 HANDLE fileDescriptor = openFile(openRequest);
474 if (fileDescriptor == INVALID_HANDLE_VALUE) {
475 openRequest.result.emplace<QFileDevice::FileError>(QFileDevice::FileError::OpenError);
478 result.fd = qintptr(fileDescriptor);
480 invokeCallback(openRequest);
481 return RequestPrepResult::RequestCompleted;
484 if (ongoingSplitOperations > 0)
485 return RequestPrepResult::Defer;
489 auto *closeRequest = request.requestData<Operation::Close>();
491 const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd));
492 hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA,
493 quintptr(std::addressof(request)),
494 IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
498 auto *readRequest = request.requestData<Operation::Read>();
499 auto span = readRequest->destination;
500 auto offset = readRequest->offset;
501 if (span.size() > MaxReadWriteLen) {
502 qCDebug(lcQIORing) <<
"Requested Read of size" << span.size() <<
"has to be split";
504 if (extra->spanOffset == 0)
505 ++ongoingSplitOperations;
506 const qsizetype remaining = span.size() - extra->spanOffset;
507 span.slice(extra->spanOffset,
std::min(remaining, MaxReadWriteLen));
508 offset += extra->totalProcessed;
510 hr = buildReadOperation(ioRingHandle, readRequest->fd, span, offset,
511 quintptr(std::addressof(request)));
515 auto *vectoredReadRequest = request.requestData<Operation::VectoredRead>();
516 auto span = vectoredReadRequest->destinations.front();
517 auto offset = vectoredReadRequest->offset;
518 if (Q_LIKELY(vectoredReadRequest->destinations.size() > 1
519 || span.size() > MaxReadWriteLen)) {
521 if (extra->spanOffset == 0 && extra->spanIndex == 0)
522 ++ongoingSplitOperations;
523 extra->numSpans = vectoredReadRequest->destinations.size();
525 span = vectoredReadRequest->destinations[extra->spanIndex];
527 const qsizetype remaining = span.size() - extra->spanOffset;
528 span.slice(extra->spanOffset,
std::min(remaining, MaxReadWriteLen));
529 offset += extra->totalProcessed;
531 hr = buildReadOperation(ioRingHandle, vectoredReadRequest->fd, span,
532 offset, quintptr(std::addressof(request)));
536 auto *writeRequest = request.requestData<Operation::Write>();
537 auto span = writeRequest->source;
538 auto offset = writeRequest->offset;
539 if (span.size() > MaxReadWriteLen) {
540 qCDebug(lcQIORing) <<
"Requested Write of size" << span.size() <<
"has to be split";
542 if (extra->spanOffset == 0)
543 ++ongoingSplitOperations;
544 const qsizetype remaining = span.size() - extra->spanOffset;
545 span.slice(extra->spanOffset,
std::min(remaining, MaxReadWriteLen));
546 offset += extra->totalProcessed;
548 hr = buildWriteOperation(ioRingHandle, writeRequest->fd, span, offset,
549 quintptr(std::addressof(request)));
553 auto *vectoredWriteRequest = request.requestData<Operation::VectoredWrite>();
554 auto span = vectoredWriteRequest->sources.front();
555 auto offset = vectoredWriteRequest->offset;
556 if (Q_LIKELY(vectoredWriteRequest->sources.size() > 1
557 || span.size() > MaxReadWriteLen)) {
559 if (extra->spanOffset == 0 && extra->spanIndex == 0)
560 ++ongoingSplitOperations;
561 extra->numSpans = vectoredWriteRequest->sources.size();
563 span = vectoredWriteRequest->sources[extra->spanIndex];
565 const qsizetype remaining = span.size() - extra->spanOffset;
566 span.slice(extra->spanOffset,
std::min(remaining, MaxReadWriteLen));
567 offset += extra->totalProcessed;
569 hr = buildWriteOperation(ioRingHandle, vectoredWriteRequest->fd, span,
570 offset, quintptr(std::addressof(request)));
574 if (ongoingSplitOperations > 0)
575 return RequestPrepResult::Defer;
576 auto *flushRequest = request.requestData<Operation::Flush>();
578 const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd));
579 hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT,
580 quintptr(std::addressof(request)),
581 IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
585 auto statRequest = request.takeRequestData<Operation::Stat>();
586 FILE_STANDARD_INFO info;
588 if (!GetFileInformationByHandleEx(HANDLE(statRequest.fd), FileStandardInfo, &info,
590 DWORD winErr = GetLastError();
591 QFileDevice::FileError error = QFileDevice::UnspecifiedError;
592 if (winErr == ERROR_FILE_NOT_FOUND || winErr == ERROR_INVALID_HANDLE)
593 error = QFileDevice::OpenError;
594 else if (winErr == ERROR_ACCESS_DENIED)
595 error = QFileDevice::PermissionsError;
596 statRequest.result.emplace<QFileDevice::FileError>(error);
599 result.size = info.EndOfFile.QuadPart;
601 invokeCallback(statRequest);
602 return RequestPrepResult::RequestCompleted;
605 auto *cancelRequest = request.requestData<Operation::Cancel>();
607 if (!otherOperation || !addrItMap.contains(otherOperation)) {
608 qCDebug(lcQIORing,
"Invalid cancel for non-existant operation");
609 invokeCallback(*cancelRequest);
610 return RequestPrepResult::RequestCompleted;
612 qCDebug(lcQIORing) <<
"Cancelling operation of type" << otherOperation->operation()
614 << (otherOperation->wasQueued() ?
"queued" :
"not queued");
615 Q_ASSERT(&request != otherOperation);
616 if (!otherOperation->wasQueued()) {
619 auto it = addrItMap.take(otherOperation);
620 finishRequestWithError(*otherOperation, QFileDevice::AbortError);
621 pendingRequests.erase(it);
622 invokeCallback(*cancelRequest);
623 return RequestPrepResult::RequestCompleted;
626 invokeOnOp(*otherOperation, [&fd](
auto *request) {
627 if constexpr (
QtPrivate::OperationHasFd_v<
decltype(*request)>)
631 qCDebug(lcQIORing,
"Invalid cancel for non-existant fd");
632 invokeCallback(*cancelRequest);
633 return RequestPrepResult::RequestCompleted;
636 const IORING_HANDLE_REF fileRef((HANDLE(fd)));
637 hr = apiTable->BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation),
638 quintptr(std::addressof(request)));
642 Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted);
645 if (hr == IORING_E_SUBMISSION_QUEUE_FULL)
646 return RequestPrepResult::QueueFull;
648 finishRequestWithError(request, QFileDevice::UnspecifiedError);
649 return RequestPrepResult::RequestCompleted;
651 request.setQueued(
true);
652 return RequestPrepResult::Ok;
658 invokeOnOp(req, [&](
auto *request) {
659 if constexpr (
QtPrivate::OperationHasFd_v<
decltype(*request)>) {
660 result = quintptr(request->fd) > 0 && quintptr(request->fd) != quintptr(INVALID_HANDLE_VALUE);
Q_CORE_EXPORT void submitRequests()
QtPrivate::Operation Operation
decltype(std::declval< const T & >().fd) DetectHasFd
constexpr bool OperationHasFd_v
static const IORingApiTable * getApiTable()
QT_REQUIRE_CONFIG(liburing)
static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan< const std::byte > source, quint64 offset, quintptr userData)
#define FOREACH_WIN_IORING_FUNCTION(Fn)
static HANDLE openFile(const QIORingRequest< QIORing::Operation::Open > &openRequest)
static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan< std::byte > destination, quint64 offset, quintptr userData)