From c3e92cf1c4f54da2b4b0b6d5f26fa1b699e740a3 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
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.
(cherry picked from commit 4df13e880671ca910cb6757ceb9851025a2fd50e)
---
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 4c350379bbe4c..3146137725b3f 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 efb727b69b5f3..b0ca21b91607f 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 f427e9c2bb177..81a5d84096352 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.