summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c')
-rw-r--r--contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c551
1 files changed, 551 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c b/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c
new file mode 100644
index 0000000..07e8c24
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c
@@ -0,0 +1,551 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// The Linux backend uses io_uring for asynchronous i/o, and falls back to
23// the "generic" threadpool implementation if liburing isn't available or
24// fails for some other reason.
25
26#include "SDL_internal.h"
27
28#ifdef HAVE_LIBURING_H
29
30#include "../SDL_sysasyncio.h"
31
32#include <liburing.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <string.h> // for strerror()
36
37static SDL_InitState liburing_init;
38
39// We could add a whole bootstrap thing like the audio/video/etc subsystems use, but let's keep this simple for now.
40static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue);
41static void (*QuitAsyncIO)(void);
42static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio);
43
44// we never link directly to liburing.
45// (this says "-ffi" which sounds like a scripting language binding thing, but the non-ffi version
46// is static-inline code we can't lookup with dlsym. This is by design.)
47static const char *liburing_library = "liburing-ffi.so.2";
48static void *liburing_handle = NULL;
49
50#define SDL_LIBURING_FUNCS \
51 SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \
52 SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \
53 SDL_LIBURING_FUNC(void, io_uring_free_probe, (struct io_uring_probe *probe)) \
54 SDL_LIBURING_FUNC(int, io_uring_opcode_supported, (const struct io_uring_probe *p, int op)) \
55 SDL_LIBURING_FUNC(struct io_uring_sqe *, io_uring_get_sqe, (struct io_uring *ring)) \
56 SDL_LIBURING_FUNC(void, io_uring_prep_read,(struct io_uring_sqe *sqe, int fd, void *buf, unsigned nbytes, __u64 offset)) \
57 SDL_LIBURING_FUNC(void, io_uring_prep_write,(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned nbytes, __u64 offset)) \
58 SDL_LIBURING_FUNC(void, io_uring_prep_close, (struct io_uring_sqe *sqe, int fd)) \
59 SDL_LIBURING_FUNC(void, io_uring_prep_fsync, (struct io_uring_sqe *sqe, int fd, unsigned fsync_flags)) \
60 SDL_LIBURING_FUNC(void, io_uring_prep_cancel, (struct io_uring_sqe *sqe, void *user_data, int flags)) \
61 SDL_LIBURING_FUNC(void, io_uring_prep_timeout, (struct io_uring_sqe *sqe, struct __kernel_timespec *ts, unsigned count, unsigned flags)) \
62 SDL_LIBURING_FUNC(void, io_uring_prep_nop, (struct io_uring_sqe *sqe)) \
63 SDL_LIBURING_FUNC(void, io_uring_sqe_set_data, (struct io_uring_sqe *sqe, void *data)) \
64 SDL_LIBURING_FUNC(void, io_uring_sqe_set_flags, (struct io_uring_sqe *sqe, unsigned flags)) \
65 SDL_LIBURING_FUNC(int, io_uring_submit, (struct io_uring *ring)) \
66 SDL_LIBURING_FUNC(int, io_uring_peek_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \
67 SDL_LIBURING_FUNC(int, io_uring_wait_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \
68 SDL_LIBURING_FUNC(int, io_uring_wait_cqe_timeout, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr, struct __kernel_timespec *ts)) \
69 SDL_LIBURING_FUNC(void, io_uring_cqe_seen, (struct io_uring *ring, struct io_uring_cqe *cqe)) \
70 SDL_LIBURING_FUNC(void, io_uring_queue_exit, (struct io_uring *ring)) \
71
72
73#define SDL_LIBURING_FUNC(ret, fn, args) typedef ret (*SDL_fntype_##fn) args;
74SDL_LIBURING_FUNCS
75#undef SDL_LIBURING_FUNC
76
77typedef struct SDL_LibUringFunctions
78{
79 #define SDL_LIBURING_FUNC(ret, fn, args) SDL_fntype_##fn fn;
80 SDL_LIBURING_FUNCS
81 #undef SDL_LIBURING_FUNC
82} SDL_LibUringFunctions;
83
84static SDL_LibUringFunctions liburing;
85
86
87typedef struct LibUringAsyncIOQueueData
88{
89 SDL_Mutex *sqe_lock;
90 SDL_Mutex *cqe_lock;
91 struct io_uring ring;
92 SDL_AtomicInt num_waiting;
93} LibUringAsyncIOQueueData;
94
95
96static void UnloadLibUringLibrary(void)
97{
98 if (liburing_library) {
99 SDL_UnloadObject(liburing_handle);
100 liburing_library = NULL;
101 }
102 SDL_zero(liburing);
103}
104
105static bool LoadLibUringSyms(void)
106{
107 #define SDL_LIBURING_FUNC(ret, fn, args) { \
108 liburing.fn = (SDL_fntype_##fn) SDL_LoadFunction(liburing_handle, #fn); \
109 if (!liburing.fn) { \
110 return false; \
111 } \
112 }
113 SDL_LIBURING_FUNCS
114 #undef SDL_LIBURING_FUNC
115 return true;
116}
117
118// we rely on the presence of liburing to handle io_uring for us. The alternative is making
119// direct syscalls into the kernel, which is undesirable. liburing both shields us from this,
120// but also smooths over some kernel version differences, etc.
121static bool LoadLibUring(void)
122{
123 bool result = true;
124
125 if (!liburing_handle) {
126 liburing_handle = SDL_LoadObject(liburing_library);
127 if (!liburing_handle) {
128 result = false;
129 // Don't call SDL_SetError(): SDL_LoadObject already did.
130 } else {
131 result = LoadLibUringSyms();
132 if (result) {
133 static const int needed_ops[] = {
134 IORING_OP_NOP,
135 IORING_OP_FSYNC,
136 IORING_OP_TIMEOUT,
137 IORING_OP_CLOSE,
138 IORING_OP_READ,
139 IORING_OP_WRITE,
140 IORING_OP_ASYNC_CANCEL
141 };
142
143 struct io_uring_probe *probe = liburing.io_uring_get_probe();
144 if (!probe) {
145 result = false;
146 } else {
147 for (int i = 0; i < SDL_arraysize(needed_ops); i++) {
148 if (!io_uring_opcode_supported(probe, needed_ops[i])) {
149 result = false;
150 break;
151 }
152 }
153 liburing.io_uring_free_probe(probe);
154 }
155 }
156
157 if (!result) {
158 UnloadLibUringLibrary();
159 }
160 }
161 }
162 return result;
163}
164
165static bool liburing_SetError(const char *what, int err)
166{
167 SDL_assert(err <= 0);
168 return SDL_SetError("%s failed: %s", what, strerror(-err));
169}
170
171static Sint64 liburing_asyncio_size(void *userdata)
172{
173 const int fd = (int) (intptr_t) userdata;
174 struct stat statbuf;
175 if (fstat(fd, &statbuf) < 0) {
176 SDL_SetError("fstat failed: %s", strerror(errno));
177 return -1;
178 }
179 return ((Sint64) statbuf.st_size);
180}
181
182// you must hold sqe_lock when calling this!
183static bool liburing_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
184{
185 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
186 const int rc = liburing.io_uring_submit(&queuedata->ring);
187 return (rc < 0) ? liburing_SetError("io_uring_submit", rc) : true;
188}
189
190static void liburing_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
191{
192 SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task));
193 if (!cancel_task) {
194 return; // oh well, the task can just finish on its own.
195 }
196
197 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
198
199 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
200 SDL_LockMutex(queuedata->sqe_lock);
201 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
202 if (!sqe) {
203 SDL_UnlockMutex(queuedata->sqe_lock);
204 SDL_free(cancel_task); // oh well, the task can just finish on its own.
205 return;
206 }
207
208 cancel_task->app_userdata = task;
209 liburing.io_uring_prep_cancel(sqe, task, 0);
210 liburing.io_uring_sqe_set_data(sqe, cancel_task);
211 liburing_asyncioqueue_queue_task(userdata, task);
212 SDL_UnlockMutex(queuedata->sqe_lock);
213}
214
215static SDL_AsyncIOTask *ProcessCQE(LibUringAsyncIOQueueData *queuedata, struct io_uring_cqe *cqe)
216{
217 if (!cqe) {
218 return NULL;
219 }
220
221 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) io_uring_cqe_get_data(cqe);
222 if (task) { // can be NULL if this was just a wakeup message, a NOP, etc.
223 if (!task->queue) { // We leave `queue` blank to signify this was a task cancellation.
224 SDL_AsyncIOTask *cancel_task = task;
225 task = (SDL_AsyncIOTask *) cancel_task->app_userdata;
226 SDL_free(cancel_task);
227 if (cqe->res >= 0) { // cancel was successful?
228 task->result = SDL_ASYNCIO_CANCELED;
229 } else {
230 task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later.
231 }
232 } else if (cqe->res < 0) {
233 task->result = SDL_ASYNCIO_FAILURE;
234 // !!! FIXME: fill in task->error.
235 } else {
236 if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->res) < task->requested_size)) {
237 task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.
238 }
239
240 // don't explicitly mark it as COMPLETE; that's the default value and a linked task might have failed in an earlier operation and this would overwrite it.
241
242 if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) {
243 task->result_size = (Uint64) cqe->res;
244 }
245 }
246
247 if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) {
248 task->flush = false;
249 task = NULL; // don't return this one, it's a linked task, so it'll arrive in a later CQE.
250 }
251 }
252
253 return task;
254}
255
256static SDL_AsyncIOTask *liburing_asyncioqueue_get_results(void *userdata)
257{
258 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
259
260 // have to hold a lock because otherwise two threads will get the same cqe until we mark it "seen". Copy and mark it right away, then process further.
261 SDL_LockMutex(queuedata->cqe_lock);
262 struct io_uring_cqe *cqe = NULL;
263 const int rc = liburing.io_uring_peek_cqe(&queuedata->ring, &cqe);
264 if (rc != 0) {
265 SDL_assert(rc == -EAGAIN); // should only fail because nothing is available at the moment.
266 SDL_UnlockMutex(queuedata->cqe_lock);
267 return NULL;
268 }
269
270 struct io_uring_cqe cqe_copy;
271 SDL_copyp(&cqe_copy, cqe); // this is only a few bytes.
272 liburing.io_uring_cqe_seen(&queuedata->ring, cqe); // let io_uring use this slot again.
273 SDL_UnlockMutex(queuedata->cqe_lock);
274
275 return ProcessCQE(queuedata, &cqe_copy);
276}
277
278static SDL_AsyncIOTask *liburing_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
279{
280 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
281 struct io_uring_cqe *cqe = NULL;
282
283 SDL_AddAtomicInt(&queuedata->num_waiting, 1);
284 if (timeoutMS < 0) {
285 liburing.io_uring_wait_cqe(&queuedata->ring, &cqe);
286 } else {
287 struct __kernel_timespec ts = { (Sint64) timeoutMS / SDL_MS_PER_SECOND, (Sint64) SDL_MS_TO_NS(timeoutMS % SDL_MS_PER_SECOND) };
288 liburing.io_uring_wait_cqe_timeout(&queuedata->ring, &cqe, &ts);
289 }
290 SDL_AddAtomicInt(&queuedata->num_waiting, -1);
291
292 // (we don't care if the wait failed for any reason, as the upcoming peek_cqe will report valid information. We just wanted the wait operation to block.)
293
294 // each thing that peeks or waits for a completion _gets the same cqe_ until we mark it as seen. So when we wake up from the wait, lock the mutex and
295 // then use peek to make sure we have a unique cqe, and other competing threads either get their own or nothing.
296 return liburing_asyncioqueue_get_results(userdata); // this just happens to do all those things.
297}
298
299static void liburing_asyncioqueue_signal(void *userdata)
300{
301 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
302 const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting);
303
304 SDL_LockMutex(queuedata->sqe_lock);
305 for (int i = 0; i < num_waiting; i++) { // !!! FIXME: is there a better way to do this than pushing a zero-timeout request for everything waiting?
306 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
307 if (sqe) {
308 static struct __kernel_timespec ts; // no wait, just wake a thread as fast as this can land in the completion queue.
309 liburing.io_uring_prep_timeout(sqe, &ts, 0, 0);
310 liburing.io_uring_sqe_set_data(sqe, NULL);
311 }
312 }
313 liburing.io_uring_submit(&queuedata->ring);
314
315 SDL_UnlockMutex(queuedata->sqe_lock);
316}
317
318static void liburing_asyncioqueue_destroy(void *userdata)
319{
320 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
321 liburing.io_uring_queue_exit(&queuedata->ring);
322 SDL_DestroyMutex(queuedata->sqe_lock);
323 SDL_DestroyMutex(queuedata->cqe_lock);
324 SDL_free(queuedata);
325}
326
327static bool SDL_SYS_CreateAsyncIOQueue_liburing(SDL_AsyncIOQueue *queue)
328{
329 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata));
330 if (!queuedata) {
331 return false;
332 }
333
334 SDL_SetAtomicInt(&queuedata->num_waiting, 0);
335
336 queuedata->sqe_lock = SDL_CreateMutex();
337 if (!queuedata->sqe_lock) {
338 SDL_free(queuedata);
339 return false;
340 }
341
342 queuedata->cqe_lock = SDL_CreateMutex();
343 if (!queuedata->cqe_lock) {
344 SDL_DestroyMutex(queuedata->sqe_lock);
345 SDL_free(queuedata);
346 return false;
347 }
348
349 // !!! FIXME: no idea how large the queue should be. Is 128 overkill or too small?
350 const int rc = liburing.io_uring_queue_init(128, &queuedata->ring, 0);
351 if (rc != 0) {
352 SDL_DestroyMutex(queuedata->sqe_lock);
353 SDL_DestroyMutex(queuedata->cqe_lock);
354 SDL_free(queuedata);
355 return liburing_SetError("io_uring_queue_init", rc);
356 }
357
358 static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_liburing = {
359 liburing_asyncioqueue_queue_task,
360 liburing_asyncioqueue_cancel_task,
361 liburing_asyncioqueue_get_results,
362 liburing_asyncioqueue_wait_results,
363 liburing_asyncioqueue_signal,
364 liburing_asyncioqueue_destroy
365 };
366
367 SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_liburing);
368 queue->userdata = queuedata;
369 return true;
370}
371
372
373static bool liburing_asyncio_read(void *userdata, SDL_AsyncIOTask *task)
374{
375 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
376 const int fd = (int) (intptr_t) userdata;
377
378 // !!! FIXME: `unsigned` is likely smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs
379 // !!! FIXME: and make a note in the task that there are several in sequence.
380 if (task->requested_size > ((Uint64) ~((unsigned) 0))) {
381 return SDL_SetError("io_uring: i/o task is too large");
382 }
383
384 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
385 SDL_LockMutex(queuedata->sqe_lock);
386 bool retval;
387 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
388 if (!sqe) {
389 retval = SDL_SetError("io_uring: submission queue is full");
390 } else {
391 liburing.io_uring_prep_read(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset);
392 liburing.io_uring_sqe_set_data(sqe, task);
393 retval = task->queue->iface.queue_task(task->queue->userdata, task);
394 }
395 SDL_UnlockMutex(queuedata->sqe_lock);
396 return retval;
397}
398
399static bool liburing_asyncio_write(void *userdata, SDL_AsyncIOTask *task)
400{
401 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
402 const int fd = (int) (intptr_t) userdata;
403
404 // !!! FIXME: `unsigned` is likely smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs
405 // !!! FIXME: and make a note in the task that there are several in sequence.
406 if (task->requested_size > ((Uint64) ~((unsigned) 0))) {
407 return SDL_SetError("io_uring: i/o task is too large");
408 }
409
410 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
411 SDL_LockMutex(queuedata->sqe_lock);
412 bool retval;
413 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
414 if (!sqe) {
415 retval = SDL_SetError("io_uring: submission queue is full");
416 } else {
417 liburing.io_uring_prep_write(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset);
418 liburing.io_uring_sqe_set_data(sqe, task);
419 retval = task->queue->iface.queue_task(task->queue->userdata, task);
420 }
421 SDL_UnlockMutex(queuedata->sqe_lock);
422 return retval;
423}
424
425static bool liburing_asyncio_close(void *userdata, SDL_AsyncIOTask *task)
426{
427 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
428 const int fd = (int) (intptr_t) userdata;
429
430 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
431 SDL_LockMutex(queuedata->sqe_lock);
432 bool retval;
433 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
434 if (!sqe) {
435 retval = SDL_SetError("io_uring: submission queue is full");
436 } else {
437 if (task->flush) {
438 struct io_uring_sqe *flush_sqe = sqe;
439 sqe = liburing.io_uring_get_sqe(&queuedata->ring); // this will be our actual close task.
440 if (!sqe) {
441 liburing.io_uring_prep_nop(flush_sqe); // we already have the first sqe, just make it a NOP.
442 liburing.io_uring_sqe_set_data(flush_sqe, NULL);
443 task->queue->iface.queue_task(task->queue->userdata, task);
444 SDL_UnlockMutex(queuedata->sqe_lock);
445 return SDL_SetError("io_uring: submission queue is full");
446 }
447 liburing.io_uring_prep_fsync(flush_sqe, fd, IORING_FSYNC_DATASYNC);
448 liburing.io_uring_sqe_set_data(flush_sqe, task);
449 liburing.io_uring_sqe_set_flags(flush_sqe, IOSQE_IO_HARDLINK); // must complete before next sqe starts, and next sqe should run even if this fails.
450 }
451
452 liburing.io_uring_prep_close(sqe, fd);
453 liburing.io_uring_sqe_set_data(sqe, task);
454
455 retval = task->queue->iface.queue_task(task->queue->userdata, task);
456 }
457 SDL_UnlockMutex(queuedata->sqe_lock);
458 return retval;
459}
460
461static void liburing_asyncio_destroy(void *userdata)
462{
463 // this is only a Unix file descriptor, should have been closed elsewhere.
464}
465
466static int PosixOpenModeFromString(const char *mode)
467{
468 // this is exactly the set of strings that SDL_AsyncIOFromFile promises will work.
469 static const struct { const char *str; int flags; } mappings[] = {
470 { "rb", O_RDONLY },
471 { "wb", O_WRONLY | O_CREAT | O_TRUNC },
472 { "r+b", O_RDWR },
473 { "w+b", O_RDWR | O_CREAT | O_TRUNC }
474 };
475
476 for (int i = 0; i < SDL_arraysize(mappings); i++) {
477 if (SDL_strcmp(mappings[i].str, mode) == 0) {
478 return mappings[i].flags;
479 }
480 }
481
482 SDL_assert(!"Shouldn't have reached this code");
483 return 0;
484}
485
486static bool SDL_SYS_AsyncIOFromFile_liburing(const char *file, const char *mode, SDL_AsyncIO *asyncio)
487{
488 const int fd = open(file, PosixOpenModeFromString(mode), 0644);
489 if (fd == -1) {
490 return SDL_SetError("open failed: %s", strerror(errno));
491 }
492
493 static const SDL_AsyncIOInterface SDL_AsyncIOFile_liburing = {
494 liburing_asyncio_size,
495 liburing_asyncio_read,
496 liburing_asyncio_write,
497 liburing_asyncio_close,
498 liburing_asyncio_destroy
499 };
500
501 SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_liburing);
502 asyncio->userdata = (void *) (intptr_t) fd;
503 return true;
504}
505
506static void SDL_SYS_QuitAsyncIO_liburing(void)
507{
508 UnloadLibUringLibrary();
509}
510
511static void MaybeInitializeLibUring(void)
512{
513 if (SDL_ShouldInit(&liburing_init)) {
514 if (LoadLibUring()) {
515 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing;
516 QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing;
517 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing;
518 } else { // can't use liburing? Use the "generic" threadpool implementation instead.
519 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic;
520 QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic;
521 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic;
522 }
523 SDL_SetInitialized(&liburing_init, true);
524 }
525}
526
527bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
528{
529 MaybeInitializeLibUring();
530 return CreateAsyncIOQueue(queue);
531}
532
533bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
534{
535 MaybeInitializeLibUring();
536 return AsyncIOFromFile(file, mode, asyncio);
537}
538
539void SDL_SYS_QuitAsyncIO(void)
540{
541 if (SDL_ShouldQuit(&liburing_init)) {
542 QuitAsyncIO();
543 CreateAsyncIOQueue = NULL;
544 QuitAsyncIO = NULL;
545 AsyncIOFromFile = NULL;
546 SDL_SetInitialized(&liburing_init, false);
547 }
548}
549
550#endif // defined HAVE_LIBURING_H
551