SDL_asyncio_liburing.c 21 KB

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