From 4df13e880671ca910cb6757ceb9851025a2fd50e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 31 Jan 2026 03:58:25 -0500 Subject: [PATCH] asyncio: don't report failures on closing read-only files with Windows IoRing. We still need the task to go through the IoRing, even though the flush operation we use to get it there will always fail on a read-only file. So check for this specific case and don't report failure. Fixes #14878. --- src/io/SDL_asyncio.c | 20 +++++++++++++------- src/io/SDL_sysasyncio.h | 1 + src/io/windows/SDL_asyncio_windows_ioring.c | 10 ++++++++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/io/SDL_asyncio.c b/src/io/SDL_asyncio.c index 4c350379bb..3146137725 100644 --- a/src/io/SDL_asyncio.c +++ b/src/io/SDL_asyncio.c @@ -23,20 +23,23 @@ #include "SDL_sysasyncio.h" #include "SDL_asyncio_c.h" -static const char *AsyncFileModeValid(const char *mode) +static const char *AsyncFileModeValid(const char *mode, bool *readonly) { - static const struct { const char *valid; const char *with_binary; } mode_map[] = { - { "r", "rb" }, - { "w", "wb" }, - { "r+","r+b" }, - { "w+", "w+b" } + static const struct { const char *valid; const char *with_binary; bool readonly; } mode_map[] = { + { "r", "rb", true }, + { "w", "wb", false }, + { "r+","r+b", false }, + { "w+", "w+b", false } }; for (int i = 0; i < SDL_arraysize(mode_map); i++) { if (SDL_strcmp(mode, mode_map[i].valid) == 0) { + *readonly = mode_map[i].readonly; return mode_map[i].with_binary; } } + + *readonly = false; return NULL; } @@ -51,7 +54,8 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) return NULL; } - const char *binary_mode = AsyncFileModeValid(mode); + bool readonly = false; + const char *binary_mode = AsyncFileModeValid(mode, &readonly); if (!binary_mode) { SDL_SetError("Unsupported file mode"); return NULL; @@ -62,6 +66,8 @@ SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) return NULL; } + asyncio->readonly = readonly; + asyncio->lock = SDL_CreateMutex(); if (!asyncio->lock) { SDL_free(asyncio); diff --git a/src/io/SDL_sysasyncio.h b/src/io/SDL_sysasyncio.h index efb727b69b..b0ca21b916 100644 --- a/src/io/SDL_sysasyncio.h +++ b/src/io/SDL_sysasyncio.h @@ -124,6 +124,7 @@ struct SDL_AsyncIO SDL_AsyncIOTask tasks; SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done. bool oneshot; // true if this is a SDL_LoadFileAsync open. + bool readonly; // true if this file is opened read-only. }; // This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata. diff --git a/src/io/windows/SDL_asyncio_windows_ioring.c b/src/io/windows/SDL_asyncio_windows_ioring.c index f427e9c2bb..81a5d84096 100644 --- a/src/io/windows/SDL_asyncio_windows_ioring.c +++ b/src/io/windows/SDL_asyncio_windows_ioring.c @@ -200,8 +200,14 @@ static SDL_AsyncIOTask *ProcessCQE(WinIoRingAsyncIOQueueData *queuedata, IORING_ task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later. } } else if (FAILED(cqe->ResultCode)) { - task->result = SDL_ASYNCIO_FAILURE; - // !!! FIXME: fill in task->error. + if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && (cqe->ResultCode == E_ACCESSDENIED) && task->asyncio->readonly) { + // we push all close requests through as flushes, as there is currently no async close operation and flushing writes to disk is the time-consuming part. + // However, flushing a read-only handle generates an error, so we catch this specific situation and ignore it. This approach still makes the task go + // through the IoRing so we can handle this all in the same place otherwise. The actual close happens below. + } else { + task->result = SDL_ASYNCIO_FAILURE; + // !!! FIXME: fill in task->error. + } } else { if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->Information) < task->requested_size)) { task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.