David Eichmann pushed to branch wip/davide/io_uring at Glasgow Haskell Compiler / GHC
Commits:
-
5c5d8bc5
by David Eichmann at 2026-08-26T11:17:22+01:00
-
622a28fe
by David Eichmann at 2026-09-04T12:02:39+01:00
12 changed files:
- docs/users_guide/runtime_control.rst
- rts/IOManager.c
- rts/IOManager.h
- rts/IOManagerInternals.h
- rts/configure.ac
- rts/include/rts/Constants.h
- rts/include/rts/Flags.h
- rts/include/rts/storage/Closures.h
- + rts/posix/IOURing.c
- + rts/posix/IOURing.h
- rts/posix/Timeout.c
- rts/rts.cabal
Changes:
| ... | ... | @@ -1445,6 +1445,7 @@ Currently the available I/O managers are: |
| 1445 | 1445 | ================ ========= ============
|
| 1446 | 1446 | ``select`` Posix Non-threaded
|
| 1447 | 1447 | ``poll`` Posix Non-threaded
|
| 1448 | +``io-uring`` Linux Non-threaded
|
|
| 1448 | 1449 | ``mio`` All Threaded
|
| 1449 | 1450 | ``win32-legacy`` Windows Non-threaded
|
| 1450 | 1451 | ``winio`` Windows Both
|
| ... | ... | @@ -1512,6 +1513,11 @@ limit can be adjusted using OS facilities (e.g. the ``ulimit`` command). |
| 1512 | 1513 | Exceeding this limit will cause the RTS (and thus typically the process) to
|
| 1513 | 1514 | terminate.
|
| 1514 | 1515 | |
| 1516 | +The ``io_uring`` I/O manager
|
|
| 1517 | +~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 1518 | + |
|
| 1519 | +TODO
|
|
| 1520 | + |
|
| 1515 | 1521 | The ``mio`` I/O manager
|
| 1516 | 1522 | ~~~~~~~~~~~~~~~~~~~~~~~
|
| 1517 | 1523 | This I/O manager is based on several platform-specific APIs. It supports
|
| ... | ... | @@ -16,8 +16,9 @@ |
| 16 | 16 | * -------------------------------------------------------------------------*/
|
| 17 | 17 | |
| 18 | 18 | #include "Rts.h"
|
| 19 | +#include "ClosureTable.h"
|
|
| 19 | 20 | #include "rts/IOInterface.h" // exported
|
| 20 | -#include "IOManager.h" // RTS internal
|
|
| 21 | +#include "IOManager.h" // RTS internal
|
|
| 21 | 22 | #include "Capability.h"
|
| 22 | 23 | #include "Schedule.h"
|
| 23 | 24 | #include "Prelude.h"
|
| ... | ... | @@ -37,14 +38,19 @@ |
| 37 | 38 | #include "posix/Timeout.h"
|
| 38 | 39 | #endif
|
| 39 | 40 | |
| 41 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 42 | +#include "posix/IOURing.h"
|
|
| 43 | +#include "posix/Timeout.h"
|
|
| 44 | +#endif
|
|
| 45 | + |
|
| 40 | 46 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 41 | -#include "posix/MIO.h"
|
|
| 42 | 47 | #include "Prelude.h"
|
| 48 | +#include "posix/MIO.h"
|
|
| 43 | 49 | #endif
|
| 44 | 50 | |
| 45 | 51 | #if defined(IOMGR_ENABLED_MIO_WIN32)
|
| 46 | -#include "win32/ThrIOManager.h"
|
|
| 47 | 52 | #include "win32/AsyncMIO.h"
|
| 53 | +#include "win32/ThrIOManager.h"
|
|
| 48 | 54 | #endif
|
| 49 | 55 | |
| 50 | 56 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| ... | ... | @@ -55,8 +61,8 @@ |
| 55 | 61 | #endif
|
| 56 | 62 | |
| 57 | 63 | #if defined(IOMGR_ENABLED_WINIO)
|
| 58 | -#include "win32/ThrIOManager.h"
|
|
| 59 | 64 | #include "win32/AsyncWinIO.h"
|
| 65 | +#include "win32/ThrIOManager.h"
|
|
| 60 | 66 | #if !defined(THREADED_RTS)
|
| 61 | 67 | #include "win32/AwaitEvent.h"
|
| 62 | 68 | #endif
|
| ... | ... | @@ -64,7 +70,6 @@ |
| 64 | 70 | |
| 65 | 71 | #include <string.h>
|
| 66 | 72 | |
| 67 | - |
|
| 68 | 73 | /* We have lots of functions below with conditional implementations for
|
| 69 | 74 | * different I/O managers. Some functions, for some I/O managers, naturally
|
| 70 | 75 | * have implementations that do nothing or barf. When only one such I/O
|
| ... | ... | @@ -90,11 +95,11 @@ |
| 90 | 95 | * We put this check here since it has to live somewhere, and the main users
|
| 91 | 96 | * of StgAsyncIOOp are I/O managers.
|
| 92 | 97 | */
|
| 93 | -GHC_STATIC_ASSERT(sizeof(StgAsyncIOOp)
|
|
| 94 | - == sizeof(StgHeader)
|
|
| 95 | - + sizeof(StgPtr) * stg_ASYNCIOOP_NUM_PTRS
|
|
| 96 | - + sizeof(StgWord) * stg_ASYNCIOOP_NUM_NONPTRS,
|
|
| 97 | - "sizeof(StgAsyncIOOp) does not match expected size");
|
|
| 98 | +GHC_STATIC_ASSERT(sizeof(StgAsyncIOOp) ==
|
|
| 99 | + sizeof(StgHeader) +
|
|
| 100 | + sizeof(StgPtr) * stg_ASYNCIOOP_NUM_PTRS +
|
|
| 101 | + sizeof(StgWord) * stg_ASYNCIOOP_NUM_NONPTRS,
|
|
| 102 | + "sizeof(StgAsyncIOOp) does not match expected size");
|
|
| 98 | 103 | |
| 99 | 104 | /* Global var to tell us which I/O manager impl we are using */
|
| 100 | 105 | IOManagerType iomgr_type;
|
| ... | ... | @@ -107,95 +112,94 @@ IOManagerType iomgr_type; |
| 107 | 112 | bool rts_IOManagerIsWin32Native = false;
|
| 108 | 113 | #endif
|
| 109 | 114 | |
| 110 | -enum IOManagerAvailability
|
|
| 111 | -parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
|
|
| 112 | -{
|
|
| 113 | - if (strcmp("select", iomgrstr) == 0) {
|
|
| 115 | +enum IOManagerAvailability parseIOManagerFlag(const char *iomgrstr,
|
|
| 116 | + IO_MANAGER_FLAG *flag) {
|
|
| 117 | + if (strcmp("select", iomgrstr) == 0) {
|
|
| 114 | 118 | #if defined(IOMGR_ENABLED_SELECT)
|
| 115 | - *flag = IO_MNGR_FLAG_SELECT;
|
|
| 116 | - return IOManagerAvailable;
|
|
| 119 | + *flag = IO_MNGR_FLAG_SELECT;
|
|
| 120 | + return IOManagerAvailable;
|
|
| 117 | 121 | #else
|
| 118 | - return IOManagerUnavailable;
|
|
| 122 | + return IOManagerUnavailable;
|
|
| 119 | 123 | #endif
|
| 120 | - }
|
|
| 121 | - else if (strcmp("poll", iomgrstr) == 0) {
|
|
| 124 | + } else if (strcmp("poll", iomgrstr) == 0) {
|
|
| 122 | 125 | #if defined(IOMGR_ENABLED_POLL)
|
| 123 | - *flag = IO_MNGR_FLAG_POLL;
|
|
| 124 | - return IOManagerAvailable;
|
|
| 126 | + *flag = IO_MNGR_FLAG_POLL;
|
|
| 127 | + return IOManagerAvailable;
|
|
| 125 | 128 | #else
|
| 126 | - return IOManagerUnavailable;
|
|
| 129 | + return IOManagerUnavailable;
|
|
| 127 | 130 | #endif
|
| 128 | - }
|
|
| 129 | - else if (strcmp("mio", iomgrstr) == 0) {
|
|
| 131 | + } else if (strcmp("io-uring", iomgrstr) == 0) {
|
|
| 132 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 133 | + *flag = IO_MNGR_FLAG_IO_URING;
|
|
| 134 | + return IOManagerAvailable;
|
|
| 135 | +#else
|
|
| 136 | + return IOManagerUnavailable;
|
|
| 137 | +#endif
|
|
| 138 | + } else if (strcmp("mio", iomgrstr) == 0) {
|
|
| 130 | 139 | #if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
|
| 131 | - *flag = IO_MNGR_FLAG_MIO;
|
|
| 132 | - return IOManagerAvailable;
|
|
| 140 | + *flag = IO_MNGR_FLAG_MIO;
|
|
| 141 | + return IOManagerAvailable;
|
|
| 133 | 142 | #else
|
| 134 | - return IOManagerUnavailable;
|
|
| 143 | + return IOManagerUnavailable;
|
|
| 135 | 144 | #endif
|
| 136 | - }
|
|
| 137 | - else if (strcmp("winio", iomgrstr) == 0) {
|
|
| 145 | + } else if (strcmp("winio", iomgrstr) == 0) {
|
|
| 138 | 146 | #if defined(IOMGR_ENABLED_WINIO)
|
| 139 | - *flag = IO_MNGR_FLAG_WINIO;
|
|
| 140 | - return IOManagerAvailable;
|
|
| 147 | + *flag = IO_MNGR_FLAG_WINIO;
|
|
| 148 | + return IOManagerAvailable;
|
|
| 141 | 149 | #else
|
| 142 | - return IOManagerUnavailable;
|
|
| 150 | + return IOManagerUnavailable;
|
|
| 143 | 151 | #endif
|
| 144 | - }
|
|
| 145 | - else if (strcmp("win32-legacy", iomgrstr) == 0) {
|
|
| 152 | + } else if (strcmp("win32-legacy", iomgrstr) == 0) {
|
|
| 146 | 153 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 147 | - *flag = IO_MNGR_FLAG_WIN32_LEGACY;
|
|
| 148 | - return IOManagerAvailable;
|
|
| 154 | + *flag = IO_MNGR_FLAG_WIN32_LEGACY;
|
|
| 155 | + return IOManagerAvailable;
|
|
| 149 | 156 | #else
|
| 150 | - return IOManagerUnavailable;
|
|
| 151 | -#endif
|
|
| 152 | - }
|
|
| 153 | - else if (strcmp("auto", iomgrstr) == 0) {
|
|
| 154 | - *flag = IO_MNGR_FLAG_AUTO;
|
|
| 155 | - return IOManagerAvailable;
|
|
| 156 | - }
|
|
| 157 | - /* Two deprecated aliases. These aliases only had any effect on Windows,
|
|
| 158 | - * but were available as RTS flags on all platforms. The "native" flag
|
|
| 159 | - * refers to the newer Windows WinIO IO manager (threaded or non-threaded),
|
|
| 160 | - * while (somewhat confusingly) the "posix" flag refers to the older
|
|
| 161 | - * Windows I/O managers (win32-legacy and mio). On non-Windows, we now make
|
|
| 162 | - * these flags equivalent to IO_MNGR_FLAG_AUTO.
|
|
| 163 | - */
|
|
| 164 | - else if (strcmp("native", iomgrstr) == 0) {
|
|
| 157 | + return IOManagerUnavailable;
|
|
| 158 | +#endif
|
|
| 159 | + } else if (strcmp("auto", iomgrstr) == 0) {
|
|
| 160 | + *flag = IO_MNGR_FLAG_AUTO;
|
|
| 161 | + return IOManagerAvailable;
|
|
| 162 | + }
|
|
| 163 | + /* Two deprecated aliases. These aliases only had any effect on Windows,
|
|
| 164 | + * but were available as RTS flags on all platforms. The "native" flag
|
|
| 165 | + * refers to the newer Windows WinIO IO manager (threaded or non-threaded),
|
|
| 166 | + * while (somewhat confusingly) the "posix" flag refers to the older
|
|
| 167 | + * Windows I/O managers (win32-legacy and mio). On non-Windows, we now make
|
|
| 168 | + * these flags equivalent to IO_MNGR_FLAG_AUTO.
|
|
| 169 | + */
|
|
| 170 | + else if (strcmp("native", iomgrstr) == 0) {
|
|
| 165 | 171 | #if defined(mingw32_HOST_OS)
|
| 166 | 172 | /* On windows "native" is now an alias for IO_MNGR_FLAG_WINIO */
|
| 167 | 173 | #if defined(IOMGR_ENABLED_WINIO)
|
| 168 | - *flag = IO_MNGR_FLAG_WINIO;
|
|
| 169 | - return IOManagerAvailable;
|
|
| 174 | + *flag = IO_MNGR_FLAG_WINIO;
|
|
| 175 | + return IOManagerAvailable;
|
|
| 170 | 176 | #else
|
| 171 | - return IOManagerUnavailable;
|
|
| 177 | + return IOManagerUnavailable;
|
|
| 172 | 178 | #endif
|
| 173 | 179 | #else // !defined(mingw32_HOST_OS)
|
| 174 | - *flag = IO_MNGR_FLAG_AUTO;
|
|
| 175 | - return IOManagerAvailable;
|
|
| 180 | + *flag = IO_MNGR_FLAG_AUTO;
|
|
| 181 | + return IOManagerAvailable;
|
|
| 176 | 182 | #endif
|
| 177 | - }
|
|
| 178 | - else if (strcmp("posix", iomgrstr) == 0) {
|
|
| 183 | + } else if (strcmp("posix", iomgrstr) == 0) {
|
|
| 179 | 184 | #if defined(mingw32_HOST_OS)
|
| 180 | - /* On Windows "posix" is now an alias for either IO_MNGR_FLAG_MIO or
|
|
| 181 | - * IO_MNGR_FLAG_WIN32_LEGACY */
|
|
| 185 | + /* On Windows "posix" is now an alias for either IO_MNGR_FLAG_MIO or
|
|
| 186 | + * IO_MNGR_FLAG_WIN32_LEGACY */
|
|
| 182 | 187 | #if defined(IOMGR_ENABLED_MIO_WIN32)
|
| 183 | - *flag = IO_MNGR_FLAG_MIO;
|
|
| 184 | - return IOManagerAvailable;
|
|
| 188 | + *flag = IO_MNGR_FLAG_MIO;
|
|
| 189 | + return IOManagerAvailable;
|
|
| 185 | 190 | #elif defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 186 | - *flag = IO_MNGR_FLAG_WIN32_LEGACY;
|
|
| 187 | - return IOManagerAvailable;
|
|
| 191 | + *flag = IO_MNGR_FLAG_WIN32_LEGACY;
|
|
| 192 | + return IOManagerAvailable;
|
|
| 188 | 193 | #else
|
| 189 | - return IOManagerUnavailable;
|
|
| 194 | + return IOManagerUnavailable;
|
|
| 190 | 195 | #endif
|
| 191 | 196 | #else // !defined(mingw32_HOST_OS)
|
| 192 | - *flag = IO_MNGR_FLAG_AUTO;
|
|
| 193 | - return IOManagerAvailable;
|
|
| 197 | + *flag = IO_MNGR_FLAG_AUTO;
|
|
| 198 | + return IOManagerAvailable;
|
|
| 194 | 199 | #endif
|
| 195 | - }
|
|
| 196 | - else {
|
|
| 197 | - return IOManagerUnrecognised;
|
|
| 198 | - }
|
|
| 200 | + } else {
|
|
| 201 | + return IOManagerUnrecognised;
|
|
| 202 | + }
|
|
| 199 | 203 | }
|
| 200 | 204 | |
| 201 | 205 | /* Based on the I/O manager RTS flag, select an I/O manager to use.
|
| ... | ... | @@ -206,127 +210,136 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag) |
| 206 | 210 | * Called early in the RTS initialisation, after the RTS flags have been
|
| 207 | 211 | * processed.
|
| 208 | 212 | */
|
| 209 | -void selectIOManager(void)
|
|
| 210 | -{
|
|
| 211 | - switch (RtsFlags.MiscFlags.ioManager) {
|
|
| 212 | - case IO_MNGR_FLAG_AUTO:
|
|
| 213 | +void selectIOManager(void) {
|
|
| 214 | + switch (RtsFlags.MiscFlags.ioManager) {
|
|
| 215 | + case IO_MNGR_FLAG_AUTO:
|
|
| 213 | 216 | #if defined(THREADED_RTS)
|
| 214 | -#if defined(IOMGR_DEFAULT_THREADED_MIO)
|
|
| 217 | +#if defined(IOMGR_DEFAULT_THREADED_MIO)
|
|
| 215 | 218 | #if defined(mingw32_HOST_OS)
|
| 216 | - iomgr_type = IO_MANAGER_MIO_WIN32;
|
|
| 219 | + iomgr_type = IO_MANAGER_MIO_WIN32;
|
|
| 217 | 220 | #else
|
| 218 | - iomgr_type = IO_MANAGER_MIO_POSIX;
|
|
| 221 | + iomgr_type = IO_MANAGER_MIO_POSIX;
|
|
| 219 | 222 | #endif
|
| 220 | 223 | #elif defined(IOMGR_DEFAULT_THREADED_WINIO)
|
| 221 | - iomgr_type = IO_MANAGER_WINIO;
|
|
| 224 | + iomgr_type = IO_MANAGER_WINIO;
|
|
| 225 | +#elif defined(IOMGR_DEFAULT_THREADED_IO_URING)
|
|
| 226 | + iomgr_type = IO_MANAGER_IO_URING;
|
|
| 222 | 227 | #else
|
| 223 | 228 | #error No I/O default manager. See IOMGR_DEFAULT_THREADED_ flags
|
| 224 | 229 | #endif
|
| 225 | 230 | #else // !defined(THREADED_RTS)
|
| 226 | -#if defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
|
|
| 227 | - iomgr_type = IO_MANAGER_SELECT;
|
|
| 231 | +#if defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
|
|
| 232 | + iomgr_type = IO_MANAGER_SELECT;
|
|
| 228 | 233 | #elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
|
| 229 | - iomgr_type = IO_MANAGER_POLL;
|
|
| 234 | + iomgr_type = IO_MANAGER_POLL;
|
|
| 230 | 235 | #elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
|
| 231 | - iomgr_type = IO_MANAGER_WINIO;
|
|
| 236 | + iomgr_type = IO_MANAGER_WINIO;
|
|
| 232 | 237 | #elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
|
| 233 | - iomgr_type = IO_MANAGER_WIN32_LEGACY;
|
|
| 238 | + iomgr_type = IO_MANAGER_WIN32_LEGACY;
|
|
| 239 | +#elif defined(IOMGR_DEFAULT_NON_THREADED_IO_URING)
|
|
| 240 | + iomgr_type = IO_MANAGER_IO_URING;
|
|
| 234 | 241 | #else
|
| 235 | 242 | #error No I/O default manager. See IOMGR_DEFAULT_NON_THREADED_ flags
|
| 236 | 243 | #endif
|
| 237 | 244 | #endif
|
| 238 | - break;
|
|
| 245 | + break;
|
|
| 239 | 246 | |
| 240 | 247 | #if defined(IOMGR_ENABLED_SELECT)
|
| 241 | - case IO_MNGR_FLAG_SELECT:
|
|
| 242 | - iomgr_type = IO_MANAGER_SELECT;
|
|
| 243 | - break;
|
|
| 248 | + case IO_MNGR_FLAG_SELECT:
|
|
| 249 | + iomgr_type = IO_MANAGER_SELECT;
|
|
| 250 | + break;
|
|
| 251 | +#endif
|
|
| 252 | + |
|
| 253 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 254 | + case IO_MNGR_FLAG_IO_URING:
|
|
| 255 | + iomgr_type = IO_MANAGER_IO_URING;
|
|
| 256 | + break;
|
|
| 244 | 257 | #endif
|
| 245 | 258 | |
| 246 | 259 | #if defined(IOMGR_ENABLED_POLL)
|
| 247 | - case IO_MNGR_FLAG_POLL:
|
|
| 248 | - iomgr_type = IO_MANAGER_POLL;
|
|
| 249 | - break;
|
|
| 260 | + case IO_MNGR_FLAG_POLL:
|
|
| 261 | + iomgr_type = IO_MANAGER_POLL;
|
|
| 262 | + break;
|
|
| 250 | 263 | #endif
|
| 251 | 264 | |
| 252 | 265 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 253 | - case IO_MNGR_FLAG_MIO:
|
|
| 254 | - iomgr_type = IO_MANAGER_MIO_POSIX;
|
|
| 255 | - break;
|
|
| 266 | + case IO_MNGR_FLAG_MIO:
|
|
| 267 | + iomgr_type = IO_MANAGER_MIO_POSIX;
|
|
| 268 | + break;
|
|
| 256 | 269 | #endif
|
| 257 | 270 | |
| 258 | 271 | #if defined(IOMGR_ENABLED_MIO_WIN32)
|
| 259 | - case IO_MNGR_FLAG_MIO:
|
|
| 260 | - iomgr_type = IO_MANAGER_MIO_WIN32;
|
|
| 261 | - break;
|
|
| 272 | + case IO_MNGR_FLAG_MIO:
|
|
| 273 | + iomgr_type = IO_MANAGER_MIO_WIN32;
|
|
| 274 | + break;
|
|
| 262 | 275 | #endif
|
| 263 | 276 | |
| 264 | 277 | #if defined(IOMGR_ENABLED_WINIO)
|
| 265 | - case IO_MNGR_FLAG_WINIO:
|
|
| 266 | - iomgr_type = IO_MANAGER_WINIO;
|
|
| 267 | - break;
|
|
| 278 | + case IO_MNGR_FLAG_WINIO:
|
|
| 279 | + iomgr_type = IO_MANAGER_WINIO;
|
|
| 280 | + break;
|
|
| 268 | 281 | #endif
|
| 269 | 282 | |
| 270 | 283 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 271 | - case IO_MNGR_FLAG_WIN32_LEGACY:
|
|
| 272 | - iomgr_type = IO_MANAGER_WIN32_LEGACY;
|
|
| 273 | - break;
|
|
| 284 | + case IO_MNGR_FLAG_WIN32_LEGACY:
|
|
| 285 | + iomgr_type = IO_MANAGER_WIN32_LEGACY;
|
|
| 286 | + break;
|
|
| 274 | 287 | #endif
|
| 275 | 288 | |
| 276 | - default:
|
|
| 277 | - barf("selectIOManager: %d", RtsFlags.MiscFlags.ioManager);
|
|
| 278 | - }
|
|
| 289 | + default:
|
|
| 290 | + barf("selectIOManager: %d", RtsFlags.MiscFlags.ioManager);
|
|
| 291 | + }
|
|
| 279 | 292 | |
| 280 | 293 | #if defined(mingw32_HOST_OS)
|
| 281 | - rts_IOManagerIsWin32Native = iomgr_type == IO_MANAGER_WINIO;
|
|
| 294 | + rts_IOManagerIsWin32Native = iomgr_type == IO_MANAGER_WINIO;
|
|
| 282 | 295 | #endif
|
| 283 | 296 | }
|
| 284 | 297 | |
| 285 | - |
|
| 286 | -char * showIOManager(void)
|
|
| 287 | -{
|
|
| 288 | - switch (iomgr_type) {
|
|
| 298 | +char *showIOManager(void) {
|
|
| 299 | + switch (iomgr_type) {
|
|
| 289 | 300 | #if defined(IOMGR_ENABLED_SELECT)
|
| 290 | - case IO_MANAGER_SELECT:
|
|
| 291 | - return "select";
|
|
| 301 | + case IO_MANAGER_SELECT:
|
|
| 302 | + return "select";
|
|
| 292 | 303 | #endif
|
| 293 | 304 | #if defined(IOMGR_ENABLED_POLL)
|
| 294 | - case IO_MANAGER_POLL:
|
|
| 295 | - return "poll";
|
|
| 305 | + case IO_MANAGER_POLL:
|
|
| 306 | + return "poll";
|
|
| 307 | +#endif
|
|
| 308 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 309 | + case IO_MANAGER_IO_URING:
|
|
| 310 | + return "io-uring";
|
|
| 296 | 311 | #endif
|
| 297 | 312 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 298 | - case IO_MANAGER_MIO_POSIX:
|
|
| 299 | - return "mio";
|
|
| 313 | + case IO_MANAGER_MIO_POSIX:
|
|
| 314 | + return "mio";
|
|
| 300 | 315 | #endif
|
| 301 | 316 | #if defined(IOMGR_ENABLED_MIO_WIN32)
|
| 302 | - case IO_MANAGER_MIO_WIN32:
|
|
| 303 | - return "mio";
|
|
| 317 | + case IO_MANAGER_MIO_WIN32:
|
|
| 318 | + return "mio";
|
|
| 304 | 319 | #endif
|
| 305 | 320 | #if defined(IOMGR_ENABLED_WINIO)
|
| 306 | - case IO_MANAGER_WINIO:
|
|
| 307 | - return "winio";
|
|
| 321 | + case IO_MANAGER_WINIO:
|
|
| 322 | + return "winio";
|
|
| 308 | 323 | #endif
|
| 309 | 324 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 310 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 311 | - return "win32-legacy";
|
|
| 325 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 326 | + return "win32-legacy";
|
|
| 312 | 327 | #endif
|
| 313 | - default:
|
|
| 314 | - barf("showIOManager: %d", iomgr_type);
|
|
| 315 | - }
|
|
| 328 | + default:
|
|
| 329 | + barf("showIOManager: %d", iomgr_type);
|
|
| 330 | + }
|
|
| 316 | 331 | }
|
| 317 | 332 | |
| 318 | 333 | /* Allocate a CapIOManager for a given Capability. Having this helps us keep
|
| 319 | 334 | * struct CapIOManager opaque from most of the rest of the RTS.
|
| 320 | 335 | */
|
| 321 | -CapIOManager *allocCapabilityIOManager(Capability *cap)
|
|
| 322 | -{
|
|
| 323 | - CapIOManager *iomgr = stgMallocBytes(sizeof(CapIOManager),
|
|
| 324 | - "allocCapabilityIOManager");
|
|
| 325 | - iomgr->cap = cap; /* link back */
|
|
| 326 | - return iomgr;
|
|
| 336 | +CapIOManager *allocCapabilityIOManager(Capability *cap) {
|
|
| 337 | + CapIOManager *iomgr =
|
|
| 338 | + stgMallocBytes(sizeof(CapIOManager), "allocCapabilityIOManager");
|
|
| 339 | + iomgr->cap = cap; /* link back */
|
|
| 340 | + return iomgr;
|
|
| 327 | 341 | }
|
| 328 | 342 | |
| 329 | - |
|
| 330 | 343 | /* Initialise the per-capability CapIOManager that lives in each Capability.
|
| 331 | 344 | * Called from initCapability(), which is done in the RTS startup in
|
| 332 | 345 | * initCapabilities(), and later at runtime via setNumCapabilities().
|
| ... | ... | @@ -334,539 +347,555 @@ CapIOManager *allocCapabilityIOManager(Capability *cap) |
| 334 | 347 | * Note that during RTS startup this is called _before_ the storage manager
|
| 335 | 348 | * is initialised, so this is not allowed to allocate on the GC heap.
|
| 336 | 349 | */
|
| 337 | -void initCapabilityIOManager(CapIOManager *iomgr)
|
|
| 338 | -{
|
|
| 339 | - debugTrace(DEBUG_iomanager, "initialising I/O manager %s for cap %d",
|
|
| 340 | - showIOManager(), iomgr->cap->no);
|
|
| 350 | +void initCapabilityIOManager(CapIOManager *iomgr) {
|
|
| 351 | + debugTrace(DEBUG_iomanager, "initialising I/O manager %s for cap %d",
|
|
| 352 | + showIOManager(), iomgr->cap->no);
|
|
| 341 | 353 | |
| 342 | - switch (iomgr_type) {
|
|
| 354 | + switch (iomgr_type) {
|
|
| 343 | 355 | #if defined(IOMGR_ENABLED_SELECT)
|
| 344 | - case IO_MANAGER_SELECT:
|
|
| 345 | - initCapabilityIOManagerSelect(iomgr);
|
|
| 346 | - break;
|
|
| 356 | + case IO_MANAGER_SELECT:
|
|
| 357 | + initCapabilityIOManagerSelect(iomgr);
|
|
| 358 | + break;
|
|
| 347 | 359 | #endif
|
| 348 | 360 | |
| 349 | 361 | #if defined(IOMGR_ENABLED_POLL)
|
| 350 | - case IO_MANAGER_POLL:
|
|
| 351 | - initCapabilityIOManagerPoll(iomgr);
|
|
| 352 | - break;
|
|
| 362 | + case IO_MANAGER_POLL:
|
|
| 363 | + initCapabilityIOManagerPoll(iomgr);
|
|
| 364 | + break;
|
|
| 365 | +#endif
|
|
| 366 | + |
|
| 367 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 368 | + case IO_MANAGER_IO_URING:
|
|
| 369 | + initCapabilityIOManagerIOURing(iomgr);
|
|
| 370 | + break;
|
|
| 353 | 371 | #endif
|
| 354 | 372 | |
| 355 | 373 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 356 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 357 | - iomgr->blocked_queue_hd = END_TSO_QUEUE;
|
|
| 358 | - iomgr->blocked_queue_tl = END_TSO_QUEUE;
|
|
| 359 | - break;
|
|
| 374 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 375 | + iomgr->blocked_queue_hd = END_TSO_QUEUE;
|
|
| 376 | + iomgr->blocked_queue_tl = END_TSO_QUEUE;
|
|
| 377 | + break;
|
|
| 360 | 378 | #endif
|
| 361 | 379 | |
| 362 | 380 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 363 | - case IO_MANAGER_MIO_POSIX:
|
|
| 364 | - iomgr->control_fd = -1;
|
|
| 365 | - break;
|
|
| 381 | + case IO_MANAGER_MIO_POSIX:
|
|
| 382 | + iomgr->control_fd = -1;
|
|
| 383 | + break;
|
|
| 366 | 384 | #endif
|
| 367 | - default:
|
|
| 368 | - break;
|
|
| 369 | - }
|
|
| 385 | + default:
|
|
| 386 | + break;
|
|
| 387 | + }
|
|
| 370 | 388 | }
|
| 371 | 389 | |
| 372 | - |
|
| 373 | -void freeCapabilityIOManager(CapIOManager *iomgr)
|
|
| 374 | -{
|
|
| 375 | - switch (iomgr_type) {
|
|
| 390 | +void freeCapabilityIOManager(CapIOManager *iomgr) {
|
|
| 391 | + switch (iomgr_type) {
|
|
| 376 | 392 | #if defined(IOMGR_ENABLED_SELECT)
|
| 377 | - case IO_MANAGER_SELECT:
|
|
| 378 | - freeCapabilityIOManagerSelect(iomgr);
|
|
| 379 | - break;
|
|
| 393 | + case IO_MANAGER_SELECT:
|
|
| 394 | + freeCapabilityIOManagerSelect(iomgr);
|
|
| 395 | + break;
|
|
| 380 | 396 | #endif
|
| 381 | 397 | |
| 382 | 398 | #if defined(IOMGR_ENABLED_POLL)
|
| 383 | - case IO_MANAGER_POLL:
|
|
| 384 | - freeCapabilityIOManagerPoll(iomgr);
|
|
| 385 | - break;
|
|
| 399 | + case IO_MANAGER_POLL:
|
|
| 400 | + freeCapabilityIOManagerPoll(iomgr);
|
|
| 401 | + break;
|
|
| 386 | 402 | #endif
|
| 387 | - default:
|
|
| 388 | - break;
|
|
| 389 | - }
|
|
| 390 | -}
|
|
| 391 | 403 | |
| 404 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 405 | + case IO_MANAGER_IO_URING:
|
|
| 406 | + freeCapabilityIOManagerIOURing(iomgr);
|
|
| 407 | + break;
|
|
| 408 | +#endif
|
|
| 409 | + default:
|
|
| 410 | + break;
|
|
| 411 | + }
|
|
| 412 | +}
|
|
| 392 | 413 | |
| 393 | 414 | /* Called late in the RTS initialisation
|
| 394 | 415 | */
|
| 395 | -void startIOManager(void)
|
|
| 396 | -{
|
|
| 397 | - debugTrace(DEBUG_iomanager, "starting %s I/O manager", showIOManager());
|
|
| 416 | +void startIOManager(void) {
|
|
| 417 | + debugTrace(DEBUG_iomanager, "starting %s I/O manager", showIOManager());
|
|
| 398 | 418 | |
| 399 | - switch (iomgr_type) {
|
|
| 419 | + switch (iomgr_type) {
|
|
| 400 | 420 | |
| 401 | 421 | #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
|
| 402 | 422 | #if defined(IOMGR_ENABLED_SELECT)
|
| 403 | - case IO_MANAGER_SELECT:
|
|
| 423 | + case IO_MANAGER_SELECT:
|
|
| 404 | 424 | #endif
|
| 405 | 425 | #if defined(IOMGR_ENABLED_POLL)
|
| 406 | - case IO_MANAGER_POLL:
|
|
| 426 | + case IO_MANAGER_POLL:
|
|
| 407 | 427 | #endif
|
| 408 | - /* Make the exception CAF a GC root. See initBuiltinGcRoots for
|
|
| 409 | - * similar examples. We throw this exception if a thread tries to
|
|
| 410 | - * wait on an invalid FD.
|
|
| 411 | - */
|
|
| 412 | - getStablePtr((StgPtr)blockedOnBadFD_closure);
|
|
| 413 | - break;
|
|
| 428 | + /* Make the exception CAF a GC root. See initBuiltinGcRoots for
|
|
| 429 | + * similar examples. We throw this exception if a thread tries to
|
|
| 430 | + * wait on an invalid FD.
|
|
| 431 | + */
|
|
| 432 | + getStablePtr((StgPtr)blockedOnBadFD_closure);
|
|
| 433 | + break;
|
|
| 414 | 434 | #endif
|
| 415 | 435 | |
| 416 | 436 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 417 | - case IO_MANAGER_MIO_POSIX:
|
|
| 418 | - /* Posix implementation in posix/Signals.c
|
|
| 419 | - * TODO: rename ioManagerStart to be less generic, impl specific
|
|
| 420 | - */
|
|
| 421 | - ioManagerStart();
|
|
| 422 | - break;
|
|
| 437 | + case IO_MANAGER_MIO_POSIX:
|
|
| 438 | + /* Posix implementation in posix/Signals.c
|
|
| 439 | + * TODO: rename ioManagerStart to be less generic, impl specific
|
|
| 440 | + */
|
|
| 441 | + ioManagerStart();
|
|
| 442 | + break;
|
|
| 423 | 443 | #endif
|
| 424 | 444 | #if defined(IOMGR_ENABLED_MIO_WIN32)
|
| 425 | - case IO_MANAGER_MIO_WIN32:
|
|
| 426 | - /* Win32 implementation in win32/ThrIOManager.c
|
|
| 427 | - * TODO: rename ioManagerStart to be less generic, impl specific
|
|
| 428 | - */
|
|
| 429 | - ioManagerStart();
|
|
| 430 | - break;
|
|
| 445 | + case IO_MANAGER_MIO_WIN32:
|
|
| 446 | + /* Win32 implementation in win32/ThrIOManager.c
|
|
| 447 | + * TODO: rename ioManagerStart to be less generic, impl specific
|
|
| 448 | + */
|
|
| 449 | + ioManagerStart();
|
|
| 450 | + break;
|
|
| 431 | 451 | #endif
|
| 432 | 452 | #if defined(IOMGR_ENABLED_WINIO)
|
| 433 | - case IO_MANAGER_WINIO:
|
|
| 434 | - /* The WinIO I/O manager sadly has two implementations of this
|
|
| 435 | - * startup, depending on the threading mode.
|
|
| 436 | - * TODO: rationalise this into one entry point, that internally
|
|
| 437 | - * can do different things in the two cases.
|
|
| 438 | - */
|
|
| 453 | + case IO_MANAGER_WINIO:
|
|
| 454 | + /* The WinIO I/O manager sadly has two implementations of this
|
|
| 455 | + * startup, depending on the threading mode.
|
|
| 456 | + * TODO: rationalise this into one entry point, that internally
|
|
| 457 | + * can do different things in the two cases.
|
|
| 458 | + */
|
|
| 439 | 459 | #if defined(THREADED_RTS)
|
| 440 | - /* Win32 implementation in win32/ThrIOManager.c
|
|
| 441 | - */
|
|
| 442 | - ioManagerStart();
|
|
| 460 | + /* Win32 implementation in win32/ThrIOManager.c
|
|
| 461 | + */
|
|
| 462 | + ioManagerStart();
|
|
| 443 | 463 | #else
|
| 444 | - startupAsyncWinIO();
|
|
| 464 | + startupAsyncWinIO();
|
|
| 445 | 465 | #endif
|
| 446 | - break;
|
|
| 466 | + break;
|
|
| 447 | 467 | #endif
|
| 448 | 468 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 449 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 450 | - startupAsyncIO();
|
|
| 451 | - break;
|
|
| 469 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 470 | + startupAsyncIO();
|
|
| 471 | + break;
|
|
| 452 | 472 | #endif
|
| 453 | - default:
|
|
| 454 | - break;
|
|
| 455 | - }
|
|
| 473 | + default:
|
|
| 474 | + break;
|
|
| 475 | + }
|
|
| 456 | 476 | }
|
| 457 | 477 | |
| 458 | 478 | /* Called from forkProcess in the child process on the surviving capability.
|
| 459 | 479 | */
|
| 460 | -void
|
|
| 461 | -restartIOManager(CapIOManager *iomgr, Capability **pcap)
|
|
| 462 | -{
|
|
| 480 | +void restartIOManager(CapIOManager *iomgr, Capability **pcap) {
|
|
| 463 | 481 | |
| 464 | - switch (iomgr_type) {
|
|
| 482 | + switch (iomgr_type) {
|
|
| 465 | 483 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 466 | - case IO_MANAGER_MIO_POSIX:
|
|
| 467 | - /* Posix implementation in posix/Signals.c
|
|
| 468 | - *
|
|
| 469 | - * TODO: figure out if it is necessary for the threaded MIO case
|
|
| 470 | - * for the starting of the IO manager threads to be synchronous.
|
|
| 471 | - * It would be simpler if it could start them asynchronously and
|
|
| 472 | - * thus not have to have the pcap as an inout param, that could be
|
|
| 473 | - * modified. In practice it cannot be modified anyway since in the
|
|
| 474 | - * contexts where it is called (forkProcess), there is only a
|
|
| 475 | - * single cap available.
|
|
| 476 | - */
|
|
| 477 | - ioManagerStartCap(pcap);
|
|
| 478 | - break;
|
|
| 479 | -#endif
|
|
| 480 | - /* The IO_MANAGER_SELECT needs no initialisation */
|
|
| 481 | - /* The IO_MANAGER_POLL needs no initialisation */
|
|
| 482 | - |
|
| 483 | - /* No impl for any of the Windows I/O managers, since no forking. */
|
|
| 484 | - default:
|
|
| 485 | - break;
|
|
| 486 | - }
|
|
| 487 | -}
|
|
| 484 | + case IO_MANAGER_MIO_POSIX:
|
|
| 485 | + /* Posix implementation in posix/Signals.c
|
|
| 486 | + *
|
|
| 487 | + * TODO: figure out if it is necessary for the threaded MIO case
|
|
| 488 | + * for the starting of the IO manager threads to be synchronous.
|
|
| 489 | + * It would be simpler if it could start them asynchronously and
|
|
| 490 | + * thus not have to have the pcap as an inout param, that could be
|
|
| 491 | + * modified. In practice it cannot be modified anyway since in the
|
|
| 492 | + * contexts where it is called (forkProcess), there is only a
|
|
| 493 | + * single cap available.
|
|
| 494 | + */
|
|
| 495 | + ioManagerStartCap(pcap);
|
|
| 496 | + break;
|
|
| 497 | +#endif
|
|
| 498 | + /* The IO_MANAGER_SELECT needs no initialisation */
|
|
| 499 | + /* The IO_MANAGER_POLL needs no initialisation */
|
|
| 488 | 500 | |
| 501 | + /* No impl for any of the Windows I/O managers, since no forking. */
|
|
| 502 | + default:
|
|
| 503 | + break;
|
|
| 504 | + }
|
|
| 505 | +}
|
|
| 489 | 506 | |
| 490 | 507 | /* Called from setNumCapabilities.
|
| 491 | 508 | */
|
| 492 | -void notifyIOManagerCapabilitiesChanged(CapIOManager *iomgr, Capability **pcap)
|
|
| 493 | -{
|
|
| 494 | - switch (iomgr_type) {
|
|
| 509 | +void notifyIOManagerCapabilitiesChanged(CapIOManager *iomgr,
|
|
| 510 | + Capability **pcap) {
|
|
| 511 | + switch (iomgr_type) {
|
|
| 495 | 512 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 496 | - case IO_MANAGER_MIO_POSIX:
|
|
| 497 | - rts_evalIO(pcap, ioManagerCapabilitiesChanged_closure, NULL);
|
|
| 498 | - break;
|
|
| 513 | + case IO_MANAGER_MIO_POSIX:
|
|
| 514 | + rts_evalIO(pcap, ioManagerCapabilitiesChanged_closure, NULL);
|
|
| 515 | + break;
|
|
| 499 | 516 | #endif
|
| 500 | - default:
|
|
| 501 | - break;
|
|
| 502 | - }
|
|
| 517 | + default:
|
|
| 518 | + break;
|
|
| 519 | + }
|
|
| 503 | 520 | }
|
| 504 | 521 | |
| 505 | - |
|
| 506 | 522 | /* Called in the RTS shutdown before the scheduler exits
|
| 507 | 523 | */
|
| 508 | -void
|
|
| 509 | -stopIOManager(void)
|
|
| 510 | -{
|
|
| 511 | - switch (iomgr_type) {
|
|
| 524 | +void stopIOManager(void) {
|
|
| 525 | + switch (iomgr_type) {
|
|
| 512 | 526 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 513 | - case IO_MANAGER_MIO_POSIX:
|
|
| 514 | - /* Posix implementation in posix/Signals.c
|
|
| 515 | - * TODO: rename ioManagerDie to be less generic, impl specific
|
|
| 516 | - */
|
|
| 517 | - ioManagerDie();
|
|
| 518 | - break;
|
|
| 527 | + case IO_MANAGER_MIO_POSIX:
|
|
| 528 | + /* Posix implementation in posix/Signals.c
|
|
| 529 | + * TODO: rename ioManagerDie to be less generic, impl specific
|
|
| 530 | + */
|
|
| 531 | + ioManagerDie();
|
|
| 532 | + break;
|
|
| 519 | 533 | #endif
|
| 520 | 534 | #if defined(IOMGR_ENABLED_MIO_WIN32) || defined(IOMGR_ENABLED_WINIO)
|
| 521 | 535 | #if defined(IOMGR_ENABLED_MIO_WIN32)
|
| 522 | - case IO_MANAGER_MIO_WIN32:
|
|
| 536 | + case IO_MANAGER_MIO_WIN32:
|
|
| 523 | 537 | #endif
|
| 524 | 538 | #if defined(IOMGR_ENABLED_WINIO)
|
| 525 | - case IO_MANAGER_WINIO:
|
|
| 539 | + case IO_MANAGER_WINIO:
|
|
| 526 | 540 | #endif
|
| 527 | - /* Win32 implementation in win32/ThrIOManager.c
|
|
| 528 | - * This impl is currently shared by both MIO and WinIO I/O managers
|
|
| 529 | - * TODO: rename ioManagerDie to be less generic, impl specific
|
|
| 530 | - */
|
|
| 531 | - ioManagerDie();
|
|
| 532 | - break;
|
|
| 541 | + /* Win32 implementation in win32/ThrIOManager.c
|
|
| 542 | + * This impl is currently shared by both MIO and WinIO I/O managers
|
|
| 543 | + * TODO: rename ioManagerDie to be less generic, impl specific
|
|
| 544 | + */
|
|
| 545 | + ioManagerDie();
|
|
| 546 | + break;
|
|
| 533 | 547 | #endif
|
| 534 | - default:
|
|
| 535 | - break;
|
|
| 536 | - }
|
|
| 548 | + default:
|
|
| 549 | + break;
|
|
| 550 | + }
|
|
| 537 | 551 | }
|
| 538 | 552 | |
| 539 | - |
|
| 540 | 553 | /* Called in the RTS shutdown after the scheduler exits
|
| 541 | 554 | */
|
| 542 | -void
|
|
| 543 | -exitIOManager(bool wait_threads)
|
|
| 544 | -{
|
|
| 545 | - switch (iomgr_type) {
|
|
| 555 | +void exitIOManager(bool wait_threads) {
|
|
| 556 | + switch (iomgr_type) {
|
|
| 546 | 557 | #if defined(IOMGR_ENABLED_WINIO)
|
| 547 | - case IO_MANAGER_WINIO:
|
|
| 548 | - shutdownAsyncWinIO(wait_threads);
|
|
| 549 | - break;
|
|
| 558 | + case IO_MANAGER_WINIO:
|
|
| 559 | + shutdownAsyncWinIO(wait_threads);
|
|
| 560 | + break;
|
|
| 550 | 561 | #endif
|
| 551 | 562 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 552 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 553 | - shutdownAsyncIO(wait_threads);
|
|
| 554 | - break;
|
|
| 563 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 564 | + shutdownAsyncIO(wait_threads);
|
|
| 565 | + break;
|
|
| 555 | 566 | #endif
|
| 556 | - default:
|
|
| 557 | - break;
|
|
| 558 | - }
|
|
| 567 | + default:
|
|
| 568 | + break;
|
|
| 569 | + }
|
|
| 559 | 570 | }
|
| 560 | 571 | |
| 561 | -void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr)
|
|
| 562 | -{
|
|
| 563 | - switch (iomgr_type) {
|
|
| 572 | +void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr) {
|
|
| 573 | + switch (iomgr_type) {
|
|
| 564 | 574 | #if defined(IOMGR_ENABLED_SELECT)
|
| 565 | - case IO_MANAGER_SELECT:
|
|
| 566 | - evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
|
|
| 567 | - evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
|
|
| 568 | - evac(user, (StgClosure **)(void *)&iomgr->sleeping_queue);
|
|
| 569 | - break;
|
|
| 575 | + case IO_MANAGER_SELECT:
|
|
| 576 | + evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
|
|
| 577 | + evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
|
|
| 578 | + evac(user, (StgClosure **)(void *)&iomgr->sleeping_queue);
|
|
| 579 | + break;
|
|
| 570 | 580 | #endif
|
| 571 | 581 | |
| 572 | 582 | #if defined(IOMGR_ENABLED_POLL)
|
| 573 | - case IO_MANAGER_POLL:
|
|
| 574 | - markClosureTable(evac, user, &iomgr->aiop_table);
|
|
| 575 | - evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
|
|
| 576 | - break;
|
|
| 583 | + case IO_MANAGER_POLL:
|
|
| 584 | + markClosureTable(evac, user, &iomgr->aiop_table);
|
|
| 585 | + evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
|
|
| 586 | + break;
|
|
| 577 | 587 | #endif
|
| 578 | 588 | |
| 579 | -#if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
|
| 580 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 581 | - evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
|
|
| 582 | - evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
|
|
| 583 | - break;
|
|
| 589 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 590 | + case IO_MANAGER_IO_URING:
|
|
| 591 | + markClosureTable(evac, user, &iomgr->aiop_table);
|
|
| 592 | + break;
|
|
| 584 | 593 | #endif
|
| 585 | - default:
|
|
| 586 | - break;
|
|
| 587 | - }
|
|
| 588 | -}
|
|
| 589 | 594 | |
| 595 | +#if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
|
| 596 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 597 | + evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
|
|
| 598 | + evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
|
|
| 599 | + break;
|
|
| 600 | +#endif
|
|
| 601 | + default:
|
|
| 602 | + break;
|
|
| 603 | + }
|
|
| 604 | +}
|
|
| 590 | 605 | |
| 591 | 606 | /* Declared in rts/IOInterface.h. Used only by the MIO threaded I/O manager on
|
| 592 | 607 | * Unix platforms.
|
| 593 | 608 | */
|
| 594 | 609 | #if !defined(mingw32_HOST_OS)
|
| 595 | -void
|
|
| 596 | -setIOManagerControlFd(uint32_t cap_no, int fd) {
|
|
| 610 | +void setIOManagerControlFd(uint32_t cap_no, int fd) {
|
|
| 597 | 611 | #if defined(THREADED_RTS)
|
| 598 | - if (cap_no < getNumCapabilities()) {
|
|
| 599 | - RELAXED_STORE(&getCapability(cap_no)->iomgr->control_fd, fd);
|
|
| 600 | - } else {
|
|
| 601 | - errorBelch("warning: setIOManagerControlFd called with illegal capability number.");
|
|
| 602 | - }
|
|
| 612 | + if (cap_no < getNumCapabilities()) {
|
|
| 613 | + RELAXED_STORE(&getCapability(cap_no)->iomgr->control_fd, fd);
|
|
| 614 | + } else {
|
|
| 615 | + errorBelch("warning: setIOManagerControlFd called with illegal capability "
|
|
| 616 | + "number.");
|
|
| 617 | + }
|
|
| 603 | 618 | #endif
|
| 604 | 619 | }
|
| 605 | 620 | #endif
|
| 606 | 621 | |
| 607 | - |
|
| 608 | -void pollCompletedTimeoutsOrIO(CapIOManager *iomgr)
|
|
| 609 | -{
|
|
| 610 | - debugTrace(DEBUG_iomanager, "polling for completed IO or timeouts");
|
|
| 611 | - switch (iomgr_type) {
|
|
| 622 | +void pollCompletedTimeoutsOrIO(CapIOManager *iomgr) {
|
|
| 623 | + debugTrace(DEBUG_iomanager, "polling for completed IO or timeouts");
|
|
| 624 | + switch (iomgr_type) {
|
|
| 612 | 625 | #if defined(IOMGR_ENABLED_SELECT)
|
| 613 | - case IO_MANAGER_SELECT:
|
|
| 614 | - awaitCompletedTimeoutsOrIOSelect(iomgr, false);
|
|
| 615 | - break;
|
|
| 626 | + case IO_MANAGER_SELECT:
|
|
| 627 | + awaitCompletedTimeoutsOrIOSelect(iomgr, false);
|
|
| 628 | + break;
|
|
| 616 | 629 | #endif
|
| 617 | 630 | |
| 618 | 631 | #if defined(IOMGR_ENABLED_POLL)
|
| 619 | - case IO_MANAGER_POLL:
|
|
| 620 | - pollCompletedTimeoutsOrIOPoll(iomgr);
|
|
| 621 | - break;
|
|
| 632 | + case IO_MANAGER_POLL:
|
|
| 633 | + pollCompletedTimeoutsOrIOPoll(iomgr);
|
|
| 634 | + break;
|
|
| 622 | 635 | #endif
|
| 623 | 636 | |
| 624 | -#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
|
|
| 625 | - (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
|
|
| 637 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 638 | + case IO_MANAGER_IO_URING:
|
|
| 639 | + pollCompletedTimeoutsOrIOIOURing(iomgr);
|
|
| 640 | + break;
|
|
| 641 | +#endif
|
|
| 642 | + |
|
| 643 | +#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
|
|
| 644 | + (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
|
|
| 626 | 645 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 627 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 646 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 628 | 647 | #endif
|
| 629 | 648 | #if defined(IOMGR_ENABLED_WINIO)
|
| 630 | - case IO_MANAGER_WINIO:
|
|
| 649 | + case IO_MANAGER_WINIO:
|
|
| 631 | 650 | #endif
|
| 632 | - awaitCompletedTimeoutsOrIOWin32(iomgr->cap, false);
|
|
| 633 | - break;
|
|
| 651 | + awaitCompletedTimeoutsOrIOWin32(iomgr->cap, false);
|
|
| 652 | + break;
|
|
| 634 | 653 | #endif
|
| 635 | - default:
|
|
| 636 | - barf("pollCompletedTimeoutsOrIO not implemented");
|
|
| 637 | - }
|
|
| 654 | + default:
|
|
| 655 | + barf("pollCompletedTimeoutsOrIO not implemented");
|
|
| 656 | + }
|
|
| 638 | 657 | }
|
| 639 | 658 | |
| 640 | - |
|
| 641 | -bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr)
|
|
| 642 | -{
|
|
| 643 | - debugTrace(DEBUG_iomanager, "waiting for completed IO or timeouts");
|
|
| 644 | - bool completed = true; // wait completed or interrupted?
|
|
| 645 | - switch (iomgr_type) {
|
|
| 659 | +bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr) {
|
|
| 660 | + debugTrace(DEBUG_iomanager, "waiting for completed IO or timeouts");
|
|
| 661 | + bool completed = true; // wait completed or interrupted?
|
|
| 662 | + switch (iomgr_type) {
|
|
| 646 | 663 | #if defined(IOMGR_ENABLED_SELECT)
|
| 647 | - case IO_MANAGER_SELECT:
|
|
| 648 | - completed = awaitCompletedTimeoutsOrIOSelect(iomgr, true);
|
|
| 649 | - break;
|
|
| 664 | + case IO_MANAGER_SELECT:
|
|
| 665 | + completed = awaitCompletedTimeoutsOrIOSelect(iomgr, true);
|
|
| 666 | + break;
|
|
| 650 | 667 | #endif
|
| 651 | 668 | |
| 652 | 669 | #if defined(IOMGR_ENABLED_POLL)
|
| 653 | - case IO_MANAGER_POLL:
|
|
| 654 | - completed = awaitCompletedTimeoutsOrIOPoll(iomgr);
|
|
| 655 | - break;
|
|
| 670 | + case IO_MANAGER_POLL:
|
|
| 671 | + completed = awaitCompletedTimeoutsOrIOPoll(iomgr);
|
|
| 672 | + break;
|
|
| 673 | +#endif
|
|
| 674 | + |
|
| 675 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 676 | + case IO_MANAGER_IO_URING:
|
|
| 677 | + completed = awaitCompletedTimeoutsOrIOIOURing(iomgr);
|
|
| 678 | + break;
|
|
| 656 | 679 | #endif
|
| 657 | 680 | |
| 658 | -#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
|
|
| 659 | - (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
|
|
| 681 | +#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
|
|
| 682 | + (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
|
|
| 660 | 683 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 661 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 684 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 662 | 685 | #endif
|
| 663 | 686 | #if defined(IOMGR_ENABLED_WINIO)
|
| 664 | - case IO_MANAGER_WINIO:
|
|
| 687 | + case IO_MANAGER_WINIO:
|
|
| 665 | 688 | #endif
|
| 666 | - completed = awaitCompletedTimeoutsOrIOWin32(iomgr->cap, true);
|
|
| 667 | - break;
|
|
| 689 | + completed = awaitCompletedTimeoutsOrIOWin32(iomgr->cap, true);
|
|
| 690 | + break;
|
|
| 668 | 691 | #endif
|
| 669 | - default:
|
|
| 670 | - barf("awaitCompletedTimeoutsOrIO not implemented");
|
|
| 671 | - }
|
|
| 672 | - ASSERT(!emptyRunQueue(iomgr->cap) ||
|
|
| 673 | - getSchedState() != SCHED_RUNNING ||
|
|
| 674 | - !completed);
|
|
| 675 | - return completed;
|
|
| 692 | + default:
|
|
| 693 | + barf("awaitCompletedTimeoutsOrIO not implemented");
|
|
| 694 | + }
|
|
| 695 | + ASSERT(!emptyRunQueue(iomgr->cap) || getSchedState() != SCHED_RUNNING ||
|
|
| 696 | + !completed);
|
|
| 697 | + return completed;
|
|
| 676 | 698 | }
|
| 677 | 699 | |
| 678 | - |
|
| 679 | 700 | /* Interrupt the I/O manager if it is blocked in awaitCompletedTimeoutsOrIO,
|
| 680 | 701 | * causing it to return early and return false.
|
| 681 | 702 | */
|
| 682 | -void interruptIOManager(CapIOManager *iomgr)
|
|
| 683 | -{
|
|
| 684 | - debugTrace(DEBUG_iomanager, "Interrupting the I/O manager...");
|
|
| 685 | - switch (iomgr_type) {
|
|
| 703 | +void interruptIOManager(CapIOManager *iomgr) {
|
|
| 704 | + debugTrace(DEBUG_iomanager, "Interrupting the I/O manager...");
|
|
| 705 | + switch (iomgr_type) {
|
|
| 686 | 706 | |
| 687 | 707 | #if defined(IOMGR_ENABLED_SELECT)
|
| 688 | - case IO_MANAGER_SELECT:
|
|
| 689 | - interruptIOManagerSelect(iomgr);
|
|
| 690 | - break;
|
|
| 708 | + case IO_MANAGER_SELECT:
|
|
| 709 | + interruptIOManagerSelect(iomgr);
|
|
| 710 | + break;
|
|
| 711 | +#endif
|
|
| 712 | + |
|
| 713 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 714 | + case IO_MANAGER_IO_URING:
|
|
| 715 | + interruptIOManagerIOURing(iomgr);
|
|
| 716 | + break;
|
|
| 691 | 717 | #endif
|
| 692 | 718 | |
| 693 | 719 | #if defined(IOMGR_ENABLED_POLL)
|
| 694 | - case IO_MANAGER_POLL:
|
|
| 695 | - interruptIOManagerPoll(iomgr);
|
|
| 696 | - break;
|
|
| 720 | + case IO_MANAGER_POLL:
|
|
| 721 | + interruptIOManagerPoll(iomgr);
|
|
| 722 | + break;
|
|
| 697 | 723 | #endif
|
| 698 | 724 | |
| 699 | 725 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 700 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 701 | - abandonRequestWait();
|
|
| 702 | - break;
|
|
| 726 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 727 | + abandonRequestWait();
|
|
| 728 | + break;
|
|
| 703 | 729 | #endif
|
| 704 | 730 | |
| 705 | 731 | #if defined(IOMGR_ENABLED_WINIO)
|
| 706 | - case IO_MANAGER_WINIO:
|
|
| 707 | - /* FIXME: no support yet for interrupting in WinIO I/O manager
|
|
| 708 | - * See issue #27403
|
|
| 709 | - */
|
|
| 710 | - break;
|
|
| 732 | + case IO_MANAGER_WINIO:
|
|
| 733 | + /* FIXME: no support yet for interrupting in WinIO I/O manager
|
|
| 734 | + * See issue #27403
|
|
| 735 | + */
|
|
| 736 | + break;
|
|
| 711 | 737 | #endif
|
| 712 | 738 | |
| 713 | - default:
|
|
| 714 | - break;
|
|
| 715 | - }
|
|
| 739 | + default:
|
|
| 740 | + break;
|
|
| 741 | + }
|
|
| 716 | 742 | }
|
| 717 | 743 | |
| 718 | - |
|
| 719 | 744 | /* CMM primop. Result is true on success, or false on allocation failure. */
|
| 720 | -bool syncIOWaitReady(CapIOManager *iomgr,
|
|
| 721 | - StgTSO *tso,
|
|
| 722 | - IOReadOrWrite rw,
|
|
| 723 | - HsInt fd)
|
|
| 724 | -{
|
|
| 725 | - debugTrace(DEBUG_iomanager,
|
|
| 726 | - "thread %ld waiting for %s I/O readiness on fd %d",
|
|
| 727 | - (long) tso->id, rw == IORead ? "read" : "write", (int) fd);
|
|
| 728 | - ASSERT(tso->why_blocked == NotBlocked);
|
|
| 729 | - switch (iomgr_type) {
|
|
| 745 | +bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
|
|
| 746 | + HsInt fd) {
|
|
| 747 | + debugTrace(DEBUG_iomanager,
|
|
| 748 | + "thread %ld waiting for %s I/O readiness on fd %d", (long)tso->id,
|
|
| 749 | + rw == IORead ? "read" : "write", (int)fd);
|
|
| 750 | + ASSERT(tso->why_blocked == NotBlocked);
|
|
| 751 | + switch (iomgr_type) {
|
|
| 730 | 752 | #if defined(IOMGR_ENABLED_SELECT)
|
| 731 | - case IO_MANAGER_SELECT:
|
|
| 732 | - {
|
|
| 733 | - StgThreadWhyBlocked why_blocked = (rw == IORead ? BlockedOnRead
|
|
| 734 | - : BlockedOnWrite)
|
|
| 735 | - | BlockInfoForceNonClosure;
|
|
| 736 | - tso->block_info.fd = fd;
|
|
| 737 | - appendToIOBlockedQueue(iomgr, tso);
|
|
| 738 | - RELEASE_STORE(&tso->why_blocked, why_blocked);
|
|
| 739 | - return true;
|
|
| 740 | - }
|
|
| 753 | + case IO_MANAGER_SELECT: {
|
|
| 754 | + StgThreadWhyBlocked why_blocked =
|
|
| 755 | + (rw == IORead ? BlockedOnRead : BlockedOnWrite) |
|
|
| 756 | + BlockInfoForceNonClosure;
|
|
| 757 | + tso->block_info.fd = fd;
|
|
| 758 | + appendToIOBlockedQueue(iomgr, tso);
|
|
| 759 | + RELEASE_STORE(&tso->why_blocked, why_blocked);
|
|
| 760 | + return true;
|
|
| 761 | + }
|
|
| 741 | 762 | #endif
|
| 742 | 763 | #if defined(IOMGR_ENABLED_POLL)
|
| 743 | - case IO_MANAGER_POLL:
|
|
| 744 | - return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
|
|
| 764 | + case IO_MANAGER_POLL:
|
|
| 765 | + return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
|
|
| 745 | 766 | #endif
|
| 746 | - default:
|
|
| 747 | - barf("waitRead# / waitWrite# not available for current I/O manager");
|
|
| 748 | - }
|
|
| 767 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 768 | + case IO_MANAGER_IO_URING:
|
|
| 769 | + return syncIOWaitReadyIOURing(iomgr, tso, rw, fd);
|
|
| 770 | +#endif
|
|
| 771 | + default:
|
|
| 772 | + barf("waitRead# / waitWrite# not available for current I/O manager");
|
|
| 773 | + }
|
|
| 749 | 774 | }
|
| 750 | 775 | |
| 751 | - |
|
| 752 | -void syncIOCancel(CapIOManager *iomgr, StgTSO *tso)
|
|
| 753 | -{
|
|
| 754 | - debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long) tso->id);
|
|
| 755 | - switch (iomgr_type) {
|
|
| 776 | +void syncIOCancel(CapIOManager *iomgr, StgTSO *tso) {
|
|
| 777 | + debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long)tso->id);
|
|
| 778 | + switch (iomgr_type) {
|
|
| 756 | 779 | #if defined(IOMGR_ENABLED_SELECT)
|
| 757 | - case IO_MANAGER_SELECT:
|
|
| 758 | - removeThreadFromDeQueue(iomgr->cap,
|
|
| 759 | - &iomgr->blocked_queue_hd,
|
|
| 760 | - &iomgr->blocked_queue_tl,
|
|
| 761 | - tso);
|
|
| 762 | - break;
|
|
| 780 | + case IO_MANAGER_SELECT:
|
|
| 781 | + removeThreadFromDeQueue(iomgr->cap, &iomgr->blocked_queue_hd,
|
|
| 782 | + &iomgr->blocked_queue_tl, tso);
|
|
| 783 | + break;
|
|
| 763 | 784 | #endif
|
| 764 | 785 | #if defined(IOMGR_ENABLED_POLL)
|
| 765 | - case IO_MANAGER_POLL:
|
|
| 766 | - syncIOCancelPoll(iomgr, tso);
|
|
| 767 | - break;
|
|
| 786 | + case IO_MANAGER_POLL:
|
|
| 787 | + syncIOCancelPoll(iomgr, tso);
|
|
| 788 | + break;
|
|
| 789 | +#endif
|
|
| 790 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 791 | + case IO_MANAGER_IO_URING:
|
|
| 792 | + syncIOCancelIOURing(iomgr, tso);
|
|
| 793 | + break;
|
|
| 768 | 794 | #endif
|
| 769 | 795 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 770 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 771 | - removeThreadFromDeQueue(iomgr->cap,
|
|
| 772 | - &iomgr->blocked_queue_hd,
|
|
| 773 | - &iomgr->blocked_queue_tl,
|
|
| 774 | - tso);
|
|
| 775 | - abandonWorkRequest(tso->block_info.async_reqID);
|
|
| 776 | - break;
|
|
| 777 | -#endif
|
|
| 778 | - default:
|
|
| 779 | - barf("syncIOCancel not supported for I/O manager %d", iomgr_type);
|
|
| 780 | - }
|
|
| 796 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 797 | + removeThreadFromDeQueue(iomgr->cap, &iomgr->blocked_queue_hd,
|
|
| 798 | + &iomgr->blocked_queue_tl, tso);
|
|
| 799 | + abandonWorkRequest(tso->block_info.async_reqID);
|
|
| 800 | + break;
|
|
| 801 | +#endif
|
|
| 802 | + default:
|
|
| 803 | + barf("syncIOCancel not supported for I/O manager %d", iomgr_type);
|
|
| 804 | + }
|
|
| 781 | 805 | }
|
| 782 | 806 | |
| 783 | - |
|
| 784 | 807 | #if defined(IOMGR_ENABLED_SELECT)
|
| 785 | -static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime target);
|
|
| 808 | +static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso,
|
|
| 809 | + LowResTime target);
|
|
| 786 | 810 | #endif
|
| 787 | 811 | |
| 788 | - |
|
| 789 | 812 | /* CMM primop. Result is true on success, or false on allocation failure. */
|
| 790 | -bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
|
|
| 791 | -{
|
|
| 792 | - debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id, us_delay);
|
|
| 793 | - ASSERT(tso->why_blocked == NotBlocked);
|
|
| 794 | - switch (iomgr_type) {
|
|
| 813 | +bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) {
|
|
| 814 | + debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id,
|
|
| 815 | + us_delay);
|
|
| 816 | + ASSERT(tso->why_blocked == NotBlocked);
|
|
| 817 | + switch (iomgr_type) {
|
|
| 795 | 818 | #if defined(IOMGR_ENABLED_SELECT)
|
| 796 | - case IO_MANAGER_SELECT:
|
|
| 797 | - {
|
|
| 798 | - LowResTime target = getDelayTarget(us_delay);
|
|
| 799 | - tso->block_info.target = target;
|
|
| 800 | - insertIntoSleepingQueue(iomgr, tso, target);
|
|
| 801 | - RELEASE_STORE(&tso->why_blocked, BlockedOnDelay | BlockInfoForceNonClosure);
|
|
| 802 | - return true;
|
|
| 803 | - }
|
|
| 819 | + case IO_MANAGER_SELECT: {
|
|
| 820 | + LowResTime target = getDelayTarget(us_delay);
|
|
| 821 | + tso->block_info.target = target;
|
|
| 822 | + insertIntoSleepingQueue(iomgr, tso, target);
|
|
| 823 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDelay | BlockInfoForceNonClosure);
|
|
| 824 | + return true;
|
|
| 825 | + }
|
|
| 804 | 826 | #endif
|
| 827 | +#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_IO_URING)
|
|
| 828 | + |
|
| 805 | 829 | #if defined(IOMGR_ENABLED_POLL)
|
| 806 | - case IO_MANAGER_POLL:
|
|
| 807 | - return syncDelayTimeout(iomgr, tso, us_delay);
|
|
| 830 | + case IO_MANAGER_POLL:
|
|
| 831 | +#endif
|
|
| 832 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 833 | + case IO_MANAGER_IO_URING:
|
|
| 834 | +#endif
|
|
| 835 | + return syncDelayTimeout(iomgr, tso, us_delay);
|
|
| 808 | 836 | #endif
|
| 809 | 837 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 810 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 811 | - /* It would be nice to allocate this on the heap instead as it
|
|
| 812 | - * would make the primops more consistent.
|
|
| 813 | - */
|
|
| 814 | - {
|
|
| 815 | - tso->block_info.async_reqID = addDelayRequest(us_delay);
|
|
| 816 | - |
|
| 817 | - /* Having all async-blocked threads reside on the blocked_queue
|
|
| 818 | - * simplifies matters, so set the status to OnDoProc and put the
|
|
| 819 | - * delayed thread on the blocked_queue.
|
|
| 820 | - */
|
|
| 821 | - appendToIOBlockedQueue(iomgr, tso);
|
|
| 822 | - RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
|
| 823 | - return true;
|
|
| 824 | - }
|
|
| 825 | -#endif
|
|
| 826 | - default:
|
|
| 827 | - barf("syncDelay not supported for I/O manager %d", iomgr_type);
|
|
| 838 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 839 | + /* It would be nice to allocate this on the heap instead as it
|
|
| 840 | + * would make the primops more consistent.
|
|
| 841 | + */
|
|
| 842 | + {
|
|
| 843 | + tso->block_info.async_reqID = addDelayRequest(us_delay);
|
|
| 844 | + |
|
| 845 | + /* Having all async-blocked threads reside on the blocked_queue
|
|
| 846 | + * simplifies matters, so set the status to OnDoProc and put the
|
|
| 847 | + * delayed thread on the blocked_queue.
|
|
| 848 | + */
|
|
| 849 | + appendToIOBlockedQueue(iomgr, tso);
|
|
| 850 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
|
| 851 | + return true;
|
|
| 828 | 852 | }
|
| 853 | +#endif
|
|
| 854 | + default:
|
|
| 855 | + barf("syncDelay not supported for I/O manager %d", iomgr_type);
|
|
| 856 | + }
|
|
| 829 | 857 | }
|
| 830 | 858 | |
| 831 | - |
|
| 832 | -void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso)
|
|
| 833 | -{
|
|
| 834 | - debugTrace(DEBUG_iomanager, "cancelling delay for thread %ld", (long) tso->id);
|
|
| 835 | - switch (iomgr_type) {
|
|
| 859 | +void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso) {
|
|
| 860 | + debugTrace(DEBUG_iomanager, "cancelling delay for thread %ld", (long)tso->id);
|
|
| 861 | + switch (iomgr_type) {
|
|
| 836 | 862 | #if defined(IOMGR_ENABLED_SELECT)
|
| 837 | - case IO_MANAGER_SELECT:
|
|
| 838 | - ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
|
|
| 839 | - removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
|
|
| 840 | - break;
|
|
| 863 | + case IO_MANAGER_SELECT:
|
|
| 864 | + ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
|
|
| 865 | + removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
|
|
| 866 | + break;
|
|
| 841 | 867 | #endif
|
| 842 | 868 | #if defined(IOMGR_ENABLED_POLL)
|
| 843 | - case IO_MANAGER_POLL:
|
|
| 844 | - syncDelayCancelTimeout(iomgr, tso);
|
|
| 845 | - break;
|
|
| 846 | -#endif
|
|
| 847 | - /* Note: no case for IO_MANAGER_WIN32_LEGACY despite it having a case
|
|
| 848 | - * for syncDelay above. This is because the win32 legacy I/O manager
|
|
| 849 | - * treats delay as an I/O operation, using the BlockedOnDoProc blocking
|
|
| 850 | - * reason, rather than the BlockedOnDelay reason. As a consequence,
|
|
| 851 | - * cancellation goes via syncIOCancel instead. Yes, it's a bit weird.
|
|
| 852 | - */
|
|
| 853 | - |
|
| 854 | - default:
|
|
| 855 | - barf("syncDelayCancel not supported for I/O manager %d", iomgr_type);
|
|
| 856 | - }
|
|
| 857 | -}
|
|
| 869 | + case IO_MANAGER_POLL:
|
|
| 870 | + syncDelayCancelTimeout(iomgr, tso);
|
|
| 871 | + break;
|
|
| 872 | +#endif
|
|
| 873 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 874 | + case IO_MANAGER_IO_URING:
|
|
| 875 | + syncIOCancelIOURing(iomgr, tso);
|
|
| 876 | + break;
|
|
| 877 | +#endif
|
|
| 878 | + /* Note: no case for IO_MANAGER_WIN32_LEGACY despite it having a case
|
|
| 879 | + * for syncDelay above. This is because the win32 legacy I/O manager
|
|
| 880 | + * treats delay as an I/O operation, using the BlockedOnDoProc blocking
|
|
| 881 | + * reason, rather than the BlockedOnDelay reason. As a consequence,
|
|
| 882 | + * cancellation goes via syncIOCancel instead. Yes, it's a bit weird.
|
|
| 883 | + */
|
|
| 858 | 884 | |
| 885 | + default:
|
|
| 886 | + barf("syncDelayCancel not supported for I/O manager %d", iomgr_type);
|
|
| 887 | + }
|
|
| 888 | +}
|
|
| 859 | 889 | |
| 860 | 890 | #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 861 | -void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso)
|
|
| 862 | -{
|
|
| 863 | - ASSERT(tso->_link == END_TSO_QUEUE);
|
|
| 864 | - if (iomgr->blocked_queue_hd == END_TSO_QUEUE) {
|
|
| 865 | - iomgr->blocked_queue_hd = tso;
|
|
| 866 | - } else {
|
|
| 867 | - setTSOLink(iomgr->cap, iomgr->blocked_queue_tl, tso);
|
|
| 868 | - }
|
|
| 869 | - iomgr->blocked_queue_tl = tso;
|
|
| 891 | +void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso) {
|
|
| 892 | + ASSERT(tso->_link == END_TSO_QUEUE);
|
|
| 893 | + if (iomgr->blocked_queue_hd == END_TSO_QUEUE) {
|
|
| 894 | + iomgr->blocked_queue_hd = tso;
|
|
| 895 | + } else {
|
|
| 896 | + setTSOLink(iomgr->cap, iomgr->blocked_queue_tl, tso);
|
|
| 897 | + }
|
|
| 898 | + iomgr->blocked_queue_tl = tso;
|
|
| 870 | 899 | }
|
| 871 | 900 | #endif
|
| 872 | 901 | |
| ... | ... | @@ -879,39 +908,37 @@ void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso) |
| 879 | 908 | * used. This is a wart that should be excised.
|
| 880 | 909 | */
|
| 881 | 910 | // TODO: move to Select.c and rename
|
| 882 | -static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime target)
|
|
| 883 | -{
|
|
| 884 | - StgTSO *prev = NULL;
|
|
| 885 | - StgTSO *t = iomgr->sleeping_queue;
|
|
| 886 | - while (t != END_TSO_QUEUE && t->block_info.target < target) {
|
|
| 887 | - prev = t;
|
|
| 888 | - t = t->_link;
|
|
| 889 | - }
|
|
| 890 | - |
|
| 891 | - tso->_link = t;
|
|
| 892 | - if (prev == NULL) {
|
|
| 893 | - iomgr->sleeping_queue = tso;
|
|
| 894 | - } else {
|
|
| 895 | - setTSOLink(iomgr->cap, prev, tso);
|
|
| 896 | - }
|
|
| 911 | +static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso,
|
|
| 912 | + LowResTime target) {
|
|
| 913 | + StgTSO *prev = NULL;
|
|
| 914 | + StgTSO *t = iomgr->sleeping_queue;
|
|
| 915 | + while (t != END_TSO_QUEUE && t->block_info.target < target) {
|
|
| 916 | + prev = t;
|
|
| 917 | + t = t->_link;
|
|
| 918 | + }
|
|
| 919 | + |
|
| 920 | + tso->_link = t;
|
|
| 921 | + if (prev == NULL) {
|
|
| 922 | + iomgr->sleeping_queue = tso;
|
|
| 923 | + } else {
|
|
| 924 | + setTSOLink(iomgr->cap, prev, tso);
|
|
| 925 | + }
|
|
| 897 | 926 | }
|
| 898 | 927 | #endif
|
| 899 | 928 | |
| 900 | 929 | /* Temporary compat helper function used in the Win32 I/O managers.
|
| 901 | 930 | * TODO: replace by consulting the iomgr_type global instead.
|
| 902 | 931 | */
|
| 903 | -bool is_io_mng_native_p (void)
|
|
| 904 | -{
|
|
| 905 | - switch (iomgr_type) {
|
|
| 932 | +bool is_io_mng_native_p(void) {
|
|
| 933 | + switch (iomgr_type) {
|
|
| 906 | 934 | #if defined(IOMGR_ENABLED_WINIO)
|
| 907 | - case IO_MANAGER_WINIO:
|
|
| 908 | - return true;
|
|
| 935 | + case IO_MANAGER_WINIO:
|
|
| 936 | + return true;
|
|
| 909 | 937 | #endif
|
| 910 | - default:
|
|
| 911 | - return false;
|
|
| 912 | - }
|
|
| 938 | + default:
|
|
| 939 | + return false;
|
|
| 940 | + }
|
|
| 913 | 941 | }
|
| 914 | 942 | |
| 915 | - |
|
| 916 | 943 | /* See comment above with the #pragma GCC diagnostic push */
|
| 917 | 944 | #pragma GCC diagnostic pop |
| ... | ... | @@ -61,6 +61,9 @@ extern bool rts_IOManagerIsWin32Native; |
| 61 | 61 | #if defined(IOMGR_BUILD_POLL) && !defined(THREADED_RTS)
|
| 62 | 62 | #define IOMGR_ENABLED_POLL
|
| 63 | 63 | #endif
|
| 64 | +#if defined(IOMGR_BUILD_IO_URING)
|
|
| 65 | + #define IOMGR_ENABLED_IO_URING
|
|
| 66 | +#endif
|
|
| 64 | 67 | #if defined(IOMGR_BUILD_MIO) && defined(THREADED_RTS)
|
| 65 | 68 | /* For MIO, it is really two separate I/O manager implementations: one for
|
| 66 | 69 | * Windows and one for non-Windows. This is clear from both the C code on the
|
| ... | ... | @@ -94,6 +97,8 @@ extern bool rts_IOManagerIsWin32Native; |
| 94 | 97 | #define IOMGR_DEFAULT_STR "mio"
|
| 95 | 98 | #elif defined(IOMGR_DEFAULT_THREADED_WINIO)
|
| 96 | 99 | #define IOMGR_DEFAULT_STR "winio"
|
| 100 | +#elif defined(IOMGR_DEFAULT_THREADED_IO_URING)
|
|
| 101 | + #define IOMGR_DEFAULT_STR "io_uring"
|
|
| 97 | 102 | #else
|
| 98 | 103 | #error No I/O default manager. See IOMGR_DEFAULT_THREADED_ flags
|
| 99 | 104 | #endif
|
| ... | ... | @@ -106,6 +111,8 @@ extern bool rts_IOManagerIsWin32Native; |
| 106 | 111 | #define IOMGR_DEFAULT_STR "winio"
|
| 107 | 112 | #elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
|
| 108 | 113 | #define IOMGR_DEFAULT_STR "win32-legacy"
|
| 114 | +#elif defined(IOMGR_DEFAULT_NON_THREADED_IO_URING)
|
|
| 115 | + #define IOMGR_DEFAULT_STR "io_uring"
|
|
| 109 | 116 | #else
|
| 110 | 117 | #error No I/O default manager. See IOMGR_DEFAULT_NON_THREADED_ flags
|
| 111 | 118 | #endif
|
| ... | ... | @@ -125,6 +132,11 @@ extern bool rts_IOManagerIsWin32Native; |
| 125 | 132 | #else
|
| 126 | 133 | #define IOMGR_ENABLED_STR_POLL ""
|
| 127 | 134 | #endif
|
| 135 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 136 | + #define IOMGR_ENABLED_STR_IO_URING " io_uring"
|
|
| 137 | +#else
|
|
| 138 | + #define IOMGR_ENABLED_STR_IO_URING ""
|
|
| 139 | +#endif
|
|
| 128 | 140 | #if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
|
| 129 | 141 | #define IOMGR_ENABLED_STR_MIO " mio"
|
| 130 | 142 | #else
|
| ... | ... | @@ -143,6 +155,7 @@ extern bool rts_IOManagerIsWin32Native; |
| 143 | 155 | #define IOMGRS_ENABLED_STR \
|
| 144 | 156 | IOMGR_ENABLED_STR_SELECT \
|
| 145 | 157 | IOMGR_ENABLED_STR_POLL \
|
| 158 | + IOMGR_ENABLED_STR_IO_URING \
|
|
| 146 | 159 | IOMGR_ENABLED_STR_MIO \
|
| 147 | 160 | IOMGR_ENABLED_STR_WINIO \
|
| 148 | 161 | IOMGR_ENABLED_STR_WIN32_LEGACY
|
| ... | ... | @@ -158,6 +171,9 @@ typedef enum { |
| 158 | 171 | #if defined(IOMGR_ENABLED_POLL)
|
| 159 | 172 | IO_MANAGER_POLL,
|
| 160 | 173 | #endif
|
| 174 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 175 | + IO_MANAGER_IO_URING,
|
|
| 176 | +#endif
|
|
| 161 | 177 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 162 | 178 | IO_MANAGER_MIO_POSIX,
|
| 163 | 179 | #endif
|
| ... | ... | @@ -20,6 +20,11 @@ |
| 20 | 20 | #include "TimeoutQueue.h"
|
| 21 | 21 | #endif
|
| 22 | 22 | |
| 23 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 24 | +#include <liburing.h>
|
|
| 25 | +#include "ClosureTable.h"
|
|
| 26 | +#endif
|
|
| 27 | + |
|
| 23 | 28 | #include "BeginPrivate.h"
|
| 24 | 29 | |
| 25 | 30 | /* The per-capability data structures belonging to the I/O manager.
|
| ... | ... | @@ -53,7 +58,7 @@ struct _CapIOManager { |
| 53 | 58 | #endif
|
| 54 | 59 | #endif
|
| 55 | 60 | |
| 56 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 61 | +#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_IO_URING)
|
|
| 57 | 62 | /* AIOP and timeout collections shared by several I/O manager impls */
|
| 58 | 63 | ClosureTable aiop_table;
|
| 59 | 64 | StgTimeoutQueue *timeout_queue;
|
| ... | ... | @@ -67,6 +72,54 @@ struct _CapIOManager { |
| 67 | 72 | struct pollfd *aiop_poll_table, *full_poll_table;
|
| 68 | 73 | #endif
|
| 69 | 74 | |
| 75 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 76 | + /* io_uring library structure */
|
|
| 77 | + struct io_uring *uring;
|
|
| 78 | + |
|
| 79 | + /* The number of operations submitted (by Haskell threads to the I/O
|
|
| 80 | + manager) and not yet notified of completion. */
|
|
| 81 | + int n_submitted_b; /* for blocking operations */
|
|
| 82 | + int n_submitted_nb; /* for non-blocking operations */
|
|
| 83 | + |
|
| 84 | + /* The number of operations pending in the submission queue, but not yet
|
|
| 85 | + submitted to the kernel (so not in-flight). */
|
|
| 86 | + int n_prepared_b; /* for blocking operations */
|
|
| 87 | + int n_prepared_nb; /* for non-blocking operations */
|
|
| 88 | + |
|
| 89 | + /* The number of operations submitted to the kernel but where the
|
|
| 90 | + corresponding completion has not yet been processed. */
|
|
| 91 | + int n_inflight_b; /* for blocking operations */
|
|
| 92 | + int n_inflight_nb; /* for non-blocking operations */
|
|
| 93 | + |
|
| 94 | + /* The limit on the number of operations we allow to be in-flight */
|
|
| 95 | + int limit_inflight_b; /* for blocking operations */
|
|
| 96 | + int limit_inflight_nb; /* for non-blocking operations */
|
|
| 97 | + |
|
| 98 | + /* The number of operations pending in the overflow queue (so not in the
|
|
| 99 | + submission queue or in flight) */
|
|
| 100 | + /* no overflow for blocking operations */
|
|
| 101 | + int n_overflow_nb; /* for non-blocking operations */
|
|
| 102 | + |
|
| 103 | + /* Invariants:
|
|
| 104 | + n_submitted_b = n_prepared_b + n_inflight_b
|
|
| 105 | + n_submitted_nb = n_prepared_nb + n_inflight_nb + n_overflow_nb
|
|
| 106 | + n_prepared_b + n_prepared_nb <= size of submission queue
|
|
| 107 | + */
|
|
| 108 | + |
|
| 109 | + /* A queue of threads blocked on I/O submission and a parallel queue of
|
|
| 110 | + * their corresponding SQEs. This is only used when there are more pending
|
|
| 111 | + * (non-blocking) I/O operations than the inflight limit.
|
|
| 112 | + */
|
|
| 113 | + StgTSO *overflow_tso_q_hd, *overflow_tso_q_tl;
|
|
| 114 | + struct overflow_sqe_q_t {
|
|
| 115 | + struct io_uring_sqe *sqe;
|
|
| 116 | + struct overflow_sqe_q_t *next;
|
|
| 117 | +#if defined(DEBUG)
|
|
| 118 | + StgThreadID tid;
|
|
| 119 | +#endif
|
|
| 120 | + } *overflow_sqe_q_hd, *overflow_sqe_q_tl;
|
|
| 121 | +#endif
|
|
| 122 | + |
|
| 70 | 123 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 71 | 124 | /* Thread queue for threads blocked on I/O completion. */
|
| 72 | 125 | StgTSO *blocked_queue_hd;
|
| ... | ... | @@ -381,12 +381,16 @@ GHC_IOMANAGER_ENABLE([poll], [EnableIOManagerPoll], [IOMGR_BUILD_POLL], |
| 381 | 381 | #include <poll.h>])
|
| 382 | 382 | if test "$HostOS" = "darwin" -a "$EnableIOManagerPoll" = "YES"; then
|
| 383 | 383 | dnl Sadly poll is broken on macOS. See the macOS man page:
|
| 384 | - dnl it doesn't work for device files!
|
|
| 384 | + dnl it doesn\'t work for device files!
|
|
| 385 | 385 | AC_MSG_WARN([macOS poll() does not work properly. Disabling.])
|
| 386 | 386 | EnableIOManagerPoll=NO
|
| 387 | 387 | fi
|
| 388 | 388 | fi])
|
| 389 | 389 | |
| 390 | +GHC_IOMANAGER_ENABLE([io-uring], [EnableIOManagerIOURing], [IOMGR_BUILD_IO_URING],
|
|
| 391 | + [EnableIOManagerIOURing=YES]
|
|
| 392 | +)
|
|
| 393 | + |
|
| 390 | 394 | GHC_IOMANAGER_ENABLE([mio], [EnableIOManagerMIO], [IOMGR_BUILD_MIO],
|
| 391 | 395 | [EnableIOManagerMIO=YES])
|
| 392 | 396 | |
| ... | ... | @@ -407,6 +411,7 @@ if test "$HostOS" = "mingw32"; then |
| 407 | 411 | else
|
| 408 | 412 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [select], [EnableIOManagerSelect])
|
| 409 | 413 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [poll], [EnableIOManagerPoll])
|
| 414 | + GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [io-uring], [EnableIOManagerIOURing])
|
|
| 410 | 415 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
|
| 411 | 416 | fi
|
| 412 | 417 | GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY([IOManagerNonThreadedDefault],[non-threaded])
|
| ... | ... | @@ -428,12 +433,18 @@ GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded], |
| 428 | 433 | GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
|
| 429 | 434 | [win32-legacy], [IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY])
|
| 430 | 435 | |
| 436 | +GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
|
|
| 437 | + [io-uring], [IOMGR_DEFAULT_NON_THREADED_IO_URING])
|
|
| 438 | + |
|
| 431 | 439 | GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
|
| 432 | 440 | [mio], [IOMGR_DEFAULT_THREADED_MIO])
|
| 433 | 441 | |
| 434 | 442 | GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
|
| 435 | 443 | [winio], [IOMGR_DEFAULT_THREADED_WINIO])
|
| 436 | 444 | |
| 445 | +GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
|
|
| 446 | + [io-uring], [IOMGR_DEFAULT_THREADED_IO_URING])
|
|
| 447 | + |
|
| 437 | 448 | dnl ** FFI
|
| 438 | 449 | dnl --------------------------------------------------------------
|
| 439 | 450 |
| ... | ... | @@ -315,6 +315,8 @@ |
| 315 | 315 | #define ThreadMigrating 11 /* Uses block_info.unused */
|
| 316 | 316 | #define BlockedOnDoProc 12 /* Uses block_info.async_reqID */
|
| 317 | 317 | |
| 318 | +#define BlockedOnIOSubmission 15
|
|
| 319 | + |
|
| 318 | 320 | /* Reserved values, not values that why_blocked currently use. They
|
| 319 | 321 | * are used in primop stg_threadStatuszh and must not overlap with
|
| 320 | 322 | * other why_blocked status values. They could be changed, if the
|
| ... | ... | @@ -259,6 +259,7 @@ typedef enum _IO_MANAGER_FLAG { |
| 259 | 259 | /* All other choices pick only the requested one, with no fallback. */
|
| 260 | 260 | IO_MNGR_FLAG_SELECT, /* Unix only, non-threaded RTS only */
|
| 261 | 261 | IO_MNGR_FLAG_POLL, /* Unix only, non-threaded RTS only */
|
| 262 | + IO_MNGR_FLAG_IO_URING, /* Linux only, */
|
|
| 262 | 263 | IO_MNGR_FLAG_MIO, /* cross-platform, threaded RTS only */
|
| 263 | 264 | IO_MNGR_FLAG_WINIO, /* Windows only */
|
| 264 | 265 | IO_MNGR_FLAG_WIN32_LEGACY, /* Windows only, non-threaded RTS only */
|
| ... | ... | @@ -285,6 +286,8 @@ typedef struct _MISC_FLAGS { |
| 285 | 286 | * for the linker, NULL ==> off */
|
| 286 | 287 | IO_MANAGER_FLAG ioManager; /* The I/O manager to use. */
|
| 287 | 288 | uint32_t numIoWorkerThreads; /* Number of I/O worker threads to use. */
|
| 289 | + uint32_t io_uring_sq_entries; /* io_uring submission queue size */
|
|
| 290 | + uint32_t io_uring_cq_entries; /* io_uring completion queue size */
|
|
| 288 | 291 | } MISC_FLAGS;
|
| 289 | 292 | |
| 290 | 293 | /* See Note [Synchronization of flags and base APIs] */
|
| ... | ... | @@ -728,7 +728,12 @@ union NotifyCompletion { |
| 728 | 728 | enum NotifyCompletionType {
|
| 729 | 729 | NotifyTSO = 0, // thread-synchronous I/O
|
| 730 | 730 | NotifyMVar = 1, // async I/O with MVar notification
|
| 731 | - NotifyTVar = 2 // async I/O with TVar notification
|
|
| 731 | + NotifyTVar = 2, // async I/O with TVar notification
|
|
| 732 | + // If a TSO receives an async exception while it's waiting on I/O then
|
|
| 733 | + // the TSO stops waiting but the I/O may still be outstanding, and we
|
|
| 734 | + // need the corresponding StgAsyncIOOp until the I/O completes. We use
|
|
| 735 | + // NotifyNone in this case to avoid disturbing the original TSO.
|
|
| 736 | + NotifyNone = 3
|
|
| 732 | 737 | };
|
| 733 | 738 | |
| 734 | 739 | /* A node in the leftist heap. */
|
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2021-2023
|
|
| 4 | + *
|
|
| 5 | + * An I/O manager based on the Linux io_uring API.
|
|
| 6 | + *
|
|
| 7 | + * ---------------------------------------------------------------------------*/
|
|
| 8 | + |
|
| 9 | +#include "rts/PosixSource.h"
|
|
| 10 | +#include "Rts.h"
|
|
| 11 | +#include "RtsAPI.h"
|
|
| 12 | + |
|
| 13 | +#include "IOManager.h" // defines IOMGR_ENABLED_IO_URING
|
|
| 14 | + |
|
| 15 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 16 | + |
|
| 17 | +#include "Capability.h"
|
|
| 18 | +#include "Prelude.h"
|
|
| 19 | +#include "RaiseAsync.h"
|
|
| 20 | +#include "RtsUtils.h"
|
|
| 21 | +#include "Schedule.h"
|
|
| 22 | +#include "Threads.h"
|
|
| 23 | +#include "rts/Time.h"
|
|
| 24 | + |
|
| 25 | +#include "IOURing.h"
|
|
| 26 | + |
|
| 27 | +#include <errno.h>
|
|
| 28 | +#include <fcntl.h>
|
|
| 29 | +#include <liburing.h>
|
|
| 30 | +#include <limits.h>
|
|
| 31 | +#include <poll.h> // for poll() flags POLLIN POLLOUT
|
|
| 32 | + |
|
| 33 | +#include "IOManagerInternals.h"
|
|
| 34 | +#include "Timeout.h"
|
|
| 35 | +#include "TimeoutQueue.h"
|
|
| 36 | + |
|
| 37 | +/******************************************************************************
|
|
| 38 | + |
|
| 39 | +This I/O manager is based on the Linux io_uring API. We rely on the liburing
|
|
| 40 | +library, rather than using the system calls directly.
|
|
| 41 | + |
|
| 42 | +Introduction
|
|
| 43 | +============
|
|
| 44 | + |
|
| 45 | +The io_uring API is an _almost_ generic mechanism for performing Linux syscalls
|
|
| 46 | +asynchronously. It supports a range of I/O related operations, including
|
|
| 47 | +ordinary file read and write, and waiting for I/O readiness. It works using a
|
|
| 48 | +queue to submit I/O operations, and another queue to receive I/O completions.
|
|
| 49 | +The io_uring documentation calls these the "submission queue", abbreviated SQ,
|
|
| 50 | +and "completion queue", abbreviated CQ. The corresponding queue entries are
|
|
| 51 | +abbreviated as SQEs and CQEs.
|
|
| 52 | + |
|
| 53 | +There is a single system call to both submit operations and/or wait for I/O
|
|
| 54 | +completion (or a timeout). It is also possible to poll for new entries in the
|
|
| 55 | +completion queue without using a system call at all. This fits the RTS
|
|
| 56 | +scheduler design quite well. Every time round the scheduler loop we have to
|
|
| 57 | +do a non-blocking check for I/O completion, and it is only when there are no
|
|
| 58 | +runnable threads that we want to block and wait for I/O (or timers). So in busy
|
|
| 59 | +applications there are a lot more non-blocking than blocking checks for I/O
|
|
| 60 | +completion. So being able to do the non-blocking poll without needing a system
|
|
| 61 | +call should save significantly on system calls, compared to other APIs.
|
|
| 62 | + |
|
| 63 | +We use the liburing C library, rather than the system calls directly. This
|
|
| 64 | +provides a degree of convenience and portability across kernel versions.
|
|
| 65 | + |
|
| 66 | +Synchronous I/O
|
|
| 67 | +---------------
|
|
| 68 | + |
|
| 69 | +Classically, asynchronous I/O APIs are slower than ordinary synchronous I/O
|
|
| 70 | +APIs for the case of buffered I/O reads where the requested data is already in
|
|
| 71 | +the OS page cache (if they support buffered I/O at all). This requires
|
|
| 72 | +applications that use asynchronous I/O (and thus care about performance) to use
|
|
| 73 | +it only in some cases, and use synchronous I/O when that is expected to be
|
|
| 74 | +faster. This is complex, and thus relatively few applications use asynchronous
|
|
| 75 | +I/O. Furthermore, knowing whether data is in the page cache is something that
|
|
| 76 | +only the kernel knows reliably. User space can only make educated guesses.
|
|
| 77 | + |
|
| 78 | +A major selling point of io_uring is that it (mostly) solves this problem. It
|
|
| 79 | +is designed work for buffered I/O (as well as direct I/O). Operations are
|
|
| 80 | +submitted by putting entries (SQEs) into the submission queue (SQ) and then
|
|
| 81 | +entering the kernel (io_uring_enter). The kernel starts work on all the
|
|
| 82 | +operations. If any of them can complete synchronously then the kernel can place
|
|
| 83 | +the completion entries (CQEs) into the completion queue (CQ). Thus the
|
|
| 84 | +synchronous operations complete synchronously. Furthermore, the decision about
|
|
| 85 | +whether it can complete synchronously is made dynamically by the kernel (e.g.
|
|
| 86 | +based on whether the data is available in the page cache). The result is that a
|
|
| 87 | +simple file read where the data is available in the page cache can be as quick
|
|
| 88 | +as a normal synchronous file read system call.
|
|
| 89 | + |
|
| 90 | +Overall, this allows the same API to be used for all I/O, without having to
|
|
| 91 | +guess about synchronous vs asynchronous completions. Furthermore, io_uring
|
|
| 92 | +supports both disk I/O and sockets I/O, whereas older APIs only supported one
|
|
| 93 | +or the other (see e.g. epoll for sockets and Linux/Posix AIO for disk files).
|
|
| 94 | +Overall this allows a less complex solution, by using the same relatively
|
|
| 95 | +uniform API for everything (even through the API itself is somewhat more
|
|
| 96 | +complex than other APIs).
|
|
| 97 | + |
|
| 98 | +General strategy
|
|
| 99 | +----------------
|
|
| 100 | + |
|
| 101 | +Our I/O strategy with io_uring is to prepare but not submit I/O operations in
|
|
| 102 | +the I/O primops, and then submit the operations in the scheduler. That is, in
|
|
| 103 | +the I/O primops we put the I/O operations (SQEs) into the submission queue (SQ),
|
|
| 104 | +but we don't yet call into the kernel to inform it of the new operations.
|
|
| 105 | +Instead we do that in the scheduler. At this point in the scheduler, we can
|
|
| 106 | +both submit any pending I/O operations and handle any completions. This lets us
|
|
| 107 | +submit and collect I/O with a single system call. Handling I/O completions
|
|
| 108 | +typically results in waking up threads, which the scheduler can then deal with.
|
|
| 109 | + |
|
| 110 | +Consider the important example of simple disk reads that complete synchronously.
|
|
| 111 | +In this case the completion is available immediately after io_uring_enter
|
|
| 112 | +returns and the scheduler can reschedule the thread that submitted the I/O. So
|
|
| 113 | +the end result _should_ be as fast as a normal synchronous blocking read()
|
|
| 114 | +system call (but benchmarks are needed to verify this).
|
|
| 115 | + |
|
| 116 | +Important fast-path cases
|
|
| 117 | +-------------------------
|
|
| 118 | + |
|
| 119 | +There are a few important special fast-path cases to keep in mind:
|
|
| 120 | + |
|
| 121 | +1. Common case: as above, a Haskell thread has pending I/O, so we submit
|
|
| 122 | + it. This requires a system call. Afterwards we also process any
|
|
| 123 | + completions. This means any I/O operations that complete synchronously
|
|
| 124 | + get handled immediately and with a single system call. This is crucial
|
|
| 125 | + for performance of ordinary buffered I/O in the common case that the data
|
|
| 126 | + is available in the page cache.
|
|
| 127 | + |
|
| 128 | +2. Common case: no pending I/O to submit, but there is outstanding I/O
|
|
| 129 | + that we need to see if it has completed. This does not require a
|
|
| 130 | + system call. We just need to look at the completion ring.
|
|
| 131 | + |
|
| 132 | +3. Rare case: we have pending I/O to submit but the kernel refuses to
|
|
| 133 | + accept more I/O because the completion queue is full. In this case
|
|
| 134 | + we process the completion queue first, and then retry. This ends up
|
|
| 135 | + using two system calls.
|
|
| 136 | + |
|
| 137 | +I/O submission overload
|
|
| 138 | +-----------------------
|
|
| 139 | + |
|
| 140 | +Asynchronous I/O APIs enable I/O to be submitted without waiting and thus they
|
|
| 141 | +must deal with the problem of having too much I/O in flight at once for the
|
|
| 142 | +resources available.
|
|
| 143 | + |
|
| 144 | +For example, epoll_ctl will return ENOMEM or ENOSPC if it cannot allocate the
|
|
| 145 | +necessary memory or hits a resource limit. The epoll backend for MIO turns
|
|
| 146 | +these failures into Haskell exceptions. This works ok in practice for epoll
|
|
| 147 | +because epoll only supports one kind of async I/O operation: waiting for I/O
|
|
| 148 | +readiness. This has relatively low resource use and so systems generally hit
|
|
| 149 | +other resource limits first, e.g. file descriptors.
|
|
| 150 | + |
|
| 151 | +On the other hand io_uring supports a variety of I/O operations with different
|
|
| 152 | +behaviour and resource use. They can roughly be divided into two categories:
|
|
| 153 | +1. cheap blocking operations; and
|
|
| 154 | +2. expensive non-blocking operations.
|
|
| 155 | + |
|
| 156 | +The cheap blocking operations include waiting for I/O readiness on pipes and
|
|
| 157 | +sockets, as with epoll, but it also includes blocking reads/write/send/recv on
|
|
| 158 | +pipes and sockets. These operations tend to have very low resource use and
|
|
| 159 | +correspondingly the kernel can support very many outstanding operations. This
|
|
| 160 | +is needed for some large scale networking use cases where there can be very
|
|
| 161 | +many sockets in use at once.
|
|
| 162 | + |
|
| 163 | +The comparatively expensive non-blocking operations include read and write on
|
|
| 164 | +disk files and various file and file system operations. These operations tend
|
|
| 165 | +to have higher resource use and so the kernel cannot support too many of them
|
|
| 166 | +in progress at once. Furthermore, for non-blocking operations there isn't much
|
|
| 167 | +need to have huge numbers in progress at once: the performance benefit is
|
|
| 168 | +limited by the hardware concurrency (CPUs, SSD queue depth etc).
|
|
| 169 | + |
|
| 170 | +Unfortunately, the limits on the number of concurrent operations of each kind
|
|
| 171 | +is not known, or at least not reported by the kernel in advance. When trying
|
|
| 172 | +to submit new operations, the kernel can report EAGAIN if it is out of
|
|
| 173 | +resources. This reporting mechanism is awkward because of the two classes of
|
|
| 174 | +operations. The way we would like to respond to hitting a limit is also
|
|
| 175 | +different for blocking vs non-blocking operations.
|
|
| 176 | + |
|
| 177 | +For non-blocking operations, we can simply wait for some operations to complete
|
|
| 178 | +and then submit more. This works because non-blocking operations will
|
|
| 179 | +eventually complete, and indeed typically complete fairly promptly.
|
|
| 180 | + |
|
| 181 | +For blocking operations on the other hand, there's not a lot we can do if we
|
|
| 182 | +hit a resource limit. Waiting may not help. Blocking operations can block
|
|
| 183 | +indefinitely. It is also possible to deadlock if not all I/O readiness
|
|
| 184 | +notifications are active simultaneously. So if we hit a resource limit for
|
|
| 185 | +these operations then we can't do much better than throw exceptions to the
|
|
| 186 | +Haskell threads submitting the operations. This is of course what the MIO epoll
|
|
| 187 | +backend does.
|
|
| 188 | + |
|
| 189 | +We will make the assumption that the kernel does _not_ have separate limits for
|
|
| 190 | +these two classes of operations, but assume that it is a common limit based on
|
|
| 191 | +the common resource of kernel memory. As noted above, there is no great benefit
|
|
| 192 | +to having excessive concurrency of non-blocking operations. We can limit the
|
|
| 193 | +concurrency (using a queue) with no change in semantics of the application. On
|
|
| 194 | +the other hand an application that wants to wait on zillions of blocking
|
|
| 195 | +operations cannot have that concurrency reduced without it having a semantic
|
|
| 196 | +effect on the application. We either have to support it or (hopefully
|
|
| 197 | +gracefully) fail as resources run out.
|
|
| 198 | + |
|
| 199 | +So we take the approach of trying to limit the concurrency and thus resource
|
|
| 200 | +use of the non-blocking operations so that the remaining kernel memory can
|
|
| 201 | +maximise the number of blocking operations we can support. We do that by
|
|
| 202 | +imposing a "reasonable" limit on the number of concurrent non-blocking
|
|
| 203 | +operations. We also impose a separate larger limit on the number of concurrent
|
|
| 204 | +blocking operations. The high level idea is that we treat the non-blocking
|
|
| 205 | +limit like a semaphore (with that concurrency limit) on the threads submitting
|
|
| 206 | +non-blocking I/O operations. This means we can handle overload transparently.
|
|
| 207 | +On the other hand the limit on blocking operations is a hard limit and if we
|
|
| 208 | +hit that then we have to throw exceptions (to the threads submitting the I/O).
|
|
| 209 | + |
|
| 210 | +In the typical application use cases this strategy should avoid encountering
|
|
| 211 | +EAGAIN in the first place, however it can still happen and we must handle it
|
|
| 212 | +somehow.
|
|
| 213 | + |
|
| 214 | +Before considering EAGAIN, consider a more normal scenario with lots of
|
|
| 215 | +concurrent I/O -- both blocking and non-blocking operations -- where we do hit
|
|
| 216 | +the limit on non-blocking operations. We track the number of blocking and
|
|
| 217 | +non-blocking operations that are currently "in flight" using the counters
|
|
| 218 | +iomgr->uring_inflight_{non}blocking_aiops. These are incremented by the number
|
|
| 219 | +of prepared operations when the submission queue is successfully flushed, and
|
|
| 220 | +decremented for each completion processed. Hitting the limit means that when a
|
|
| 221 | +thread submits a new non-blocking operation we find the count of inflight
|
|
| 222 | +non-blocking operations is already at the limit, and thus a new operation would
|
|
| 223 | +be over the limit.
|
|
| 224 | + |
|
| 225 | +Normally when a thread uses a primop to submit an I/O operation we would grab
|
|
| 226 | +an existing SQE from the submission queue (SQ) and fill in the SQE. If however
|
|
| 227 | +we would go over the inflight limit, then instead we allocate a fresh SQE (on
|
|
| 228 | +the C heap using malloc) and then fill in the SQE as normal. Then we put the
|
|
| 229 | +SQE and the TSO for the thread that submitted it onto (the end of) a pair of
|
|
| 230 | +overflow queues: one for the SQEs and one for the corresponding threads. We use
|
|
| 231 | +a pair of queues rather than a queue of pairs because the TSOs are allocated on
|
|
| 232 | +the GC heap but the SQEs are on the C heap and there is existing infrastructure
|
|
| 233 | +for handling TSO queues, including tracing them for GC. In the scheduler, when
|
|
| 234 | +we process the completion of a blocking operation, if there are entries on the
|
|
| 235 | +overflow queues then we can dequeue an SQE and corresponding TSO and copy the
|
|
| 236 | +SQE into the SQ and reschedule the TSO. This means the SQE is ready to go and
|
|
| 237 | +the TSO is unblocked. If we are in this overflow situation and the completions
|
|
| 238 | +for several non-blocking operations are processed in one go then will will add
|
|
| 239 | +several SQEs to be added to the SQ in one batch. This should naturally lead to
|
|
| 240 | +batching in the overflow situation and amortise the syscall overheads.
|
|
| 241 | + |
|
| 242 | +While all of this is going on, other threads submitting _blocking_ operations
|
|
| 243 | +can proceed as normal, preparing their operations into the submission queue.
|
|
| 244 | + |
|
| 245 | +Now if we do encounter EAGAIN, we assume that it's the blocking operations
|
|
| 246 | +that are at fault.
|
|
| 247 | + |
|
| 248 | +Approaches to batching
|
|
| 249 | +----------------------
|
|
| 250 | + |
|
| 251 | +We currently do no batching, but this section discusses plausible approaches.
|
|
| 252 | + |
|
| 253 | +With this design we have the opportunity to try to accumulate multiple pending
|
|
| 254 | +I/O operations and then submit them in one batch, which could improve
|
|
| 255 | +performance in I/O intensive applications by reducing the number of system
|
|
| 256 | +calls. Ordinary synchronous I/O primops like threadWaitRead# block the Haskell
|
|
| 257 | +thread and return to the scheduler, which means we only accumulate a single
|
|
| 258 | +pending I/O operation. There are a couple ways we could achieve batching.
|
|
| 259 | + |
|
| 260 | +One approach is to allow individual Haskell threads to prepare multiple I/O
|
|
| 261 | +operations by providing asynchronous I/O primops. This could be quite effective
|
|
| 262 | +at generating a lot of I/O, but it would likely see relatively little use
|
|
| 263 | +because it requires changing application designs.
|
|
| 264 | + |
|
| 265 | +Another approach is by having the scheduler not always submit pending I/O, but
|
|
| 266 | +instead let it run other threads in the hope that they will produce more
|
|
| 267 | +pending I/O. It would wait until either a time limit or a pending count limit
|
|
| 268 | +before submitting the I/O. Such an approach would increase I/O latency:
|
|
| 269 | +consider the scheduler running a thread that creates a pending I/O operation,
|
|
| 270 | +followed by running a CPU-bound thread for a whole 20 millisecond timeslice,
|
|
| 271 | +after which the scheduler submits the I/O. Thus this approach could only be
|
|
| 272 | +used for low priority I/O, which itself would require introducing a notion of
|
|
| 273 | +I/O priority.
|
|
| 274 | + |
|
| 275 | +One can imagine variations on this design such as a more sophisticated
|
|
| 276 | +scheduler predicting if the next thread to run is likely to be I/O or CPU bound
|
|
| 277 | +and using that to decide whether to flush pending I/O or to speculate on
|
|
| 278 | +accumulating more. Or the scheduler could set a timer to interrupt CPU bound
|
|
| 279 | +threads sooner if it's speculating on gathering more I/O. This could reduce
|
|
| 280 | +the cost on average, and bound latency, but there would still be a latency vs
|
|
| 281 | +throughput tradeoff, which would almost certainly require some notion of I/O
|
|
| 282 | +priority.
|
|
| 283 | + |
|
| 284 | + |
|
| 285 | +io_uring features we use
|
|
| 286 | +------------------------
|
|
| 287 | + |
|
| 288 | +IORING_FEAT_NODROP
|
|
| 289 | + |
|
| 290 | +io_uring features we could use but don't (yet)
|
|
| 291 | +----------------------------------------------
|
|
| 292 | + |
|
| 293 | +io_uring features we cannot use
|
|
| 294 | +-------------------------------
|
|
| 295 | + |
|
| 296 | +NOTES: cannot use registered ring fd, due to multiple worker threads, even
|
|
| 297 | +though it's protected by the lock. Same for registered/direct fds.
|
|
| 298 | + |
|
| 299 | + |
|
| 300 | +Tracking counters
|
|
| 301 | +-----------------
|
|
| 302 | + |
|
| 303 | +We track the number of operations submitted (by Haskell threads to the I/O
|
|
| 304 | +manager) and not yet notified of completion:
|
|
| 305 | + |
|
| 306 | +> int n_submitted_b;
|
|
| 307 | +> int n_submitted_nb;
|
|
| 308 | + |
|
| 309 | +These are incremented when the primop submits I/O to the I/O manager, and are
|
|
| 310 | +decremented when the I/O completion is processed. We track blocking and
|
|
| 311 | +non-blocking operations separately.
|
|
| 312 | + |
|
| 313 | +We track the number of operations that are prepared in the submission queue,
|
|
| 314 | +but not yet submitted to the kernel.
|
|
| 315 | + |
|
| 316 | +> int n_prepared_b;
|
|
| 317 | +> int n_prepared_nb;
|
|
| 318 | + |
|
| 319 | +These are incremented when an operation is prepared in the submission queue
|
|
| 320 | +and decremented when operations are submitted to the kernel.
|
|
| 321 | + |
|
| 322 | +We track the number of operations submitted to the kernel and where the
|
|
| 323 | +completion has not yet been processed. We call these "in-flight" operations.
|
|
| 324 | + |
|
| 325 | +> int n_inflight_b;
|
|
| 326 | +> int n_inflight_nb;
|
|
| 327 | + |
|
| 328 | +These are incremented when operations are submitted to the kernel, and
|
|
| 329 | +decremented when I/O completions are processed.
|
|
| 330 | + |
|
| 331 | +We track the limit on the number of operations the I/O manager will allow to be
|
|
| 332 | +in-flight with the kernel.
|
|
| 333 | + |
|
| 334 | +> int limit_inflight_b;
|
|
| 335 | +> int limit_inflight_nb;
|
|
| 336 | + |
|
| 337 | +These are typically set on RTS startup and then rarely changed. The
|
|
| 338 | +limit_inflight_b can be reduced dynamically if the I/O manager encounters
|
|
| 339 | +EAGAIN when submitting operations.
|
|
| 340 | + |
|
| 341 | +We track the number of non-blocking operations that are in the overflow queue.
|
|
| 342 | +These are operations that have been submitted, and could not be put into the
|
|
| 343 | +submission queue (because it would exceed the limit_inflight_nb), and so go
|
|
| 344 | +into the overflow queue instead.
|
|
| 345 | + |
|
| 346 | +> int n_overflow_nb;
|
|
| 347 | + |
|
| 348 | +This is incremented (instead of n_prepared_nb) if the number of n_inflight_nb
|
|
| 349 | +plus n_prepared_nb is at or above the limit_inflight_nb, in which case the
|
|
| 350 | +operation (and submitting thread) is put into the overflow queue. It is
|
|
| 351 | +decremented when there is space available within the limit_inflight_nb and the
|
|
| 352 | +operation can be put into the submission queue (and thus also incrementing the
|
|
| 353 | +n_prepared_nb).
|
|
| 354 | + |
|
| 355 | +We maintain two invariants:
|
|
| 356 | + |
|
| 357 | +1. n_submitted_b = n_prepared_b + n_inflight_b
|
|
| 358 | +2. n_submitted_nb = n_prepared_nb + n_overflow_nb + n_inflight_nb
|
|
| 359 | + |
|
| 360 | +The implication is that set of prepared, overflow and inflight operations are
|
|
| 361 | +distinct from each other and their union is equal to the submitted set. Another
|
|
| 362 | +way to look at it is that there distinct states for submitted operations:
|
|
| 363 | +prepared, overflow (for non-blocking) and inflight.
|
|
| 364 | + |
|
| 365 | +******************************************************************************/
|
|
| 366 | + |
|
| 367 | +/* Forward declarations */
|
|
| 368 | +static bool enlargeTables(CapIOManager *iomgr);
|
|
| 369 | +static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop);
|
|
| 370 | +static void enqueueOverflowQueue(CapIOManager *iomgr,
|
|
| 371 | + StgTSO *tso, struct io_uring_sqe *sqe);
|
|
| 372 | +static void dequeueOverflowQueue(CapIOManager *iomgr, StgTSO **ptso,
|
|
| 373 | + struct io_uring_sqe **psqe);
|
|
| 374 | + |
|
| 375 | +/* Constants */
|
|
| 376 | + |
|
| 377 | +/* A couple tags we add to the sqe->user_data to tell us about this operation
|
|
| 378 | + * when we process the completion. Currently we distinguish:
|
|
| 379 | + * 1. non-blocking vs blocking operations, just so we can update our counters
|
|
| 380 | + * which count these separately.
|
|
| 381 | + * 2. cancellation operations
|
|
| 382 | + */
|
|
| 383 | +const uint64_t AIOP_TAG_CANCEL = 0x80lu << 56; /* bit 63 */
|
|
| 384 | +const uint64_t AIOP_TAG_OP_NB = 0x40lu << 56; /* bit 62 */
|
|
| 385 | +const uint64_t AIOP_TAG_MASK = 0xc0lu << 56; /* bit 62 | 63 */
|
|
| 386 | + |
|
| 387 | +void initCapabilityIOManagerIOURing(CapIOManager *iomgr) {
|
|
| 388 | + initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
|
|
| 389 | + iomgr->timeout_queue = emptyTimeoutQueue();
|
|
| 390 | + |
|
| 391 | + int sq_entries = RtsFlags.MiscFlags.io_uring_sq_entries;
|
|
| 392 | + |
|
| 393 | + iomgr->n_submitted_b = 0;
|
|
| 394 | + iomgr->n_submitted_nb = 0;
|
|
| 395 | + iomgr->n_prepared_b = 0;
|
|
| 396 | + iomgr->n_prepared_nb = 0;
|
|
| 397 | + iomgr->n_inflight_b = 0;
|
|
| 398 | + iomgr->n_inflight_nb = 0;
|
|
| 399 | + iomgr->limit_inflight_b = INT_MAX;
|
|
| 400 | + iomgr->limit_inflight_nb = 4 * sq_entries;
|
|
| 401 | + iomgr->n_overflow_nb = 0;
|
|
| 402 | + iomgr->overflow_tso_q_hd = END_TSO_QUEUE;
|
|
| 403 | + iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
|
|
| 404 | + iomgr->overflow_sqe_q_hd = NULL;
|
|
| 405 | + iomgr->overflow_sqe_q_tl = NULL;
|
|
| 406 | + |
|
| 407 | + /* Set the uring params: we want to use independent sizes of submission
|
|
| 408 | + * and completion queues. We typically want a bigger completion queue than
|
|
| 409 | + * a submission queue.
|
|
| 410 | + */
|
|
| 411 | + struct io_uring_params params = {
|
|
| 412 | + .flags =
|
|
| 413 | + IORING_SETUP_CQSIZE | IORING_SETUP_CLAMP | IORING_SETUP_SUBMIT_ALL,
|
|
| 414 | + .cq_entries = RtsFlags.MiscFlags.io_uring_cq_entries};
|
|
| 415 | + // TODO: what happens if we use flags that are not recognised by the kernel
|
|
| 416 | + // version, e.g. IORING_SETUP_SUBMIT_ALL prior to 5.18?
|
|
| 417 | + |
|
| 418 | + /* TODO: see if we want to support IORING_SETUP_SQPOLL. With
|
|
| 419 | + IORING_FEAT_NATIVE_WORKERS, it doesn't need any priviledges. */
|
|
| 420 | + |
|
| 421 | + /* Share the same kernel work-queue between the urings for each capability.
|
|
| 422 | + * Do this by using the IORING_SETUP_ATTACH_WQ flag for capabilities > 0,
|
|
| 423 | + * and pass the uring fd for cap 0 (the main capability).
|
|
| 424 | + *
|
|
| 425 | + * TODO why?
|
|
| 426 | + */
|
|
| 427 | + if (iomgr->cap->no > 0) {
|
|
| 428 | + params.flags |= IORING_SETUP_ATTACH_WQ;
|
|
| 429 | + params.wq_fd = MainCapability.iomgr->uring->ring_fd;
|
|
| 430 | + }
|
|
| 431 | + |
|
| 432 | + /* Try to initialise the uring */
|
|
| 433 | + struct io_uring *uring =
|
|
| 434 | + stgMallocBytes(sizeof(struct io_uring), "initCapabilityIOManagerUring");
|
|
| 435 | + int res = io_uring_queue_init_params(sq_entries, uring, ¶ms);
|
|
| 436 | + if (res < 0)
|
|
| 437 | + goto fail;
|
|
| 438 | + |
|
| 439 | + /* Check for features we require. */
|
|
| 440 | + unsigned required =
|
|
| 441 | + /* Needed for simple handling of ring sizes and limits.
|
|
| 442 | + * TODO: we might be able to support kernels without this
|
|
| 443 | + * by setting the limits to be the same as the ring sizes. */
|
|
| 444 | + IORING_FEAT_NODROP
|
|
| 445 | + |
|
| 446 | + /* TODO
|
|
| 447 | + *
|
|
| 448 | + * Early kernel versions required that the SQE struct data pointers
|
|
| 449 | + * remains valid until completion of the IO request. With
|
|
| 450 | + * IORING_FEAT_SUBMIT_STABLE such structs need only be valid untill
|
|
| 451 | + * submission into the SQ. Man pages for io_uring_prep_* functions should
|
|
| 452 | + * have a note if they are affected. The __kernel_timespec struct passed
|
|
| 453 | + * to io_uring_prep_timeout() is affected. io_uring_prep_accept() is also
|
|
| 454 | + * affected. Neither is used by this IO Manager. TODO perhaps we want this
|
|
| 455 | + * feature because it is widely supported (assuming io_uring is supported)
|
|
| 456 | + * and we may add affected IO operations in the future?
|
|
| 457 | + * See https://www.man7.org/linux/man-pages/man3/io_uring_submit.3.html
|
|
| 458 | + */
|
|
| 459 | + | IORING_FEAT_SUBMIT_STABLE
|
|
| 460 | + |
|
| 461 | + /* Needed for read/write that updates the file pos. */
|
|
| 462 | + | IORING_FEAT_RW_CUR_POS
|
|
| 463 | + |
|
| 464 | + /* Needed for corner cases like reading from /proc/self,
|
|
| 465 | + * or signalfd */
|
|
| 466 | + | IORING_FEAT_NATIVE_WORKERS;
|
|
| 467 | + |
|
| 468 | + if ((uring->features & required) != required)
|
|
| 469 | + goto fail;
|
|
| 470 | + |
|
| 471 | + /* Arrange for the uring (fd and mmap'ed queues) not to be inherited. */
|
|
| 472 | + res = fcntl(uring->ring_fd, F_SETFD, FD_CLOEXEC);
|
|
| 473 | + if (res < 0)
|
|
| 474 | + goto fail;
|
|
| 475 | + res = io_uring_ring_dontfork(uring);
|
|
| 476 | + if (res < 0)
|
|
| 477 | + goto fail;
|
|
| 478 | + |
|
| 479 | + /* Success. Save what we need. */
|
|
| 480 | + iomgr->uring = uring;
|
|
| 481 | + return;
|
|
| 482 | + |
|
| 483 | + /* Failure. Clean up. */
|
|
| 484 | +fail:
|
|
| 485 | + stgFree(uring);
|
|
| 486 | + barf("uring iomgr: initialisation failed");
|
|
| 487 | + // TODO: we should add support to fail and use a fallback I/O manager
|
|
| 488 | +}
|
|
| 489 | + |
|
| 490 | +void freeCapabilityIOManagerIOURing(CapIOManager *iomgr) {
|
|
| 491 | + io_uring_queue_exit(iomgr->uring);
|
|
| 492 | +}
|
|
| 493 | + |
|
| 494 | +/******************************************************************************
|
|
| 495 | + * Common prologues and epilogues for primops for I/O operations.
|
|
| 496 | + *
|
|
| 497 | + * There are different common prologues/epilogues depending on:
|
|
| 498 | + * - synchronous or asynchronous primops
|
|
| 499 | + * - blocking or non-blocking I/O operations
|
|
| 500 | + */
|
|
| 501 | + |
|
| 502 | +/* The common prologue for for all synchronous primops for both blocking and
|
|
| 503 | + * non-blocking I/O operations.
|
|
| 504 | + */
|
|
| 505 | +static int prologueSyncIOOp(CapIOManager *iomgr, StgTSO *tso, int why_blocked,
|
|
| 506 | + StgAsyncIOOp **paiop) {
|
|
| 507 | + StgAsyncIOOp *aiop;
|
|
| 508 | + aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp));
|
|
| 509 | + if (RTS_UNLIKELY(aiop == NULL)) {
|
|
| 510 | + return (sizeof(StgAsyncIOOp));
|
|
| 511 | + }
|
|
| 512 | + SET_HDR(aiop, &stg_ASYNCIOOP_info, CCS_SYSTEM); // TODO: get CCCS
|
|
| 513 | + aiop->notify_type = NotifyTSO;
|
|
| 514 | + aiop->notify.tso = tso;
|
|
| 515 | + tso->why_blocked = why_blocked;
|
|
| 516 | + tso->block_info.aiop = aiop;
|
|
| 517 | + *paiop = aiop;
|
|
| 518 | + return 0;
|
|
| 519 | +}
|
|
| 520 | + |
|
| 521 | +/* The common prologue for all non-blocking I/O operations.
|
|
| 522 | + *
|
|
| 523 | + * Allocate a table index
|
|
| 524 | + * Fill in some of the aiop fields
|
|
| 525 | + * Allocate an SQE, either on the ring or on the heap if we're in overflow.
|
|
| 526 | + */
|
|
| 527 | +static int prologueNonBlockingIOOp(CapIOManager *iomgr, StgTSO *tso,
|
|
| 528 | + StgAsyncIOOp *aiop,
|
|
| 529 | + struct io_uring_sqe **psqe,
|
|
| 530 | + bool *tso_block) {
|
|
| 531 | + Capability* cap = iomgr->cap;
|
|
| 532 | + if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
|
|
| 533 | + int fail = enlargeTables(iomgr);
|
|
| 534 | + if (RTS_UNLIKELY(fail))
|
|
| 535 | + return fail;
|
|
| 536 | + }
|
|
| 537 | + |
|
| 538 | + int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
|
|
| 539 | + |
|
| 540 | + aiop->capno = cap->no;
|
|
| 541 | + aiop->index = index;
|
|
| 542 | + |
|
| 543 | + struct io_uring_sqe *sqe;
|
|
| 544 | + |
|
| 545 | + if (iomgr->n_inflight_nb + iomgr->n_prepared_nb < iomgr->limit_inflight_nb) {
|
|
| 546 | + /* The typical case. Allocate an SQE from the ring */
|
|
| 547 | + sqe = io_uring_get_sqe(iomgr->uring);
|
|
| 548 | + ASSERT(sqe); /* Otherwise we counted wrong */
|
|
| 549 | + iomgr->n_submitted_nb++;
|
|
| 550 | + iomgr->n_prepared_nb++;
|
|
| 551 | + *tso_block = false;
|
|
| 552 | + } else {
|
|
| 553 | + /* We're going to have to block the submitting thread.
|
|
| 554 | + * Allocate an SQE on the heap and suspend the calling TSO.
|
|
| 555 | + */
|
|
| 556 | + sqe = stgMallocBytes(sizeof(struct io_uring_sqe), "uring iomgr");
|
|
| 557 | + enqueueOverflowQueue(iomgr, tso, sqe);
|
|
| 558 | + iomgr->n_submitted_nb++;
|
|
| 559 | + iomgr->n_overflow_nb++;
|
|
| 560 | + *tso_block = true;
|
|
| 561 | + }
|
|
| 562 | + io_uring_sqe_set_data64(sqe, index);
|
|
| 563 | + *psqe = sqe;
|
|
| 564 | + return 0;
|
|
| 565 | +}
|
|
| 566 | + |
|
| 567 | +/* The common prologue for all blocking I/O operations.
|
|
| 568 | + *
|
|
| 569 | + * Allocate a table index
|
|
| 570 | + * Fill in some of the aiop fields
|
|
| 571 | + * Allocate an SQE, or return failure if we're at the limit.
|
|
| 572 | + */
|
|
| 573 | +static bool prologueBlockingIOOp(CapIOManager *iomgr, StgTSO *tso,
|
|
| 574 | + StgAsyncIOOp *aiop,
|
|
| 575 | + struct io_uring_sqe **psqe) {
|
|
| 576 | + Capability *cap = iomgr->cap;
|
|
| 577 | + |
|
| 578 | + if (iomgr->n_inflight_b + iomgr->n_prepared_b >= iomgr->limit_inflight_b) {
|
|
| 579 | + /* If we reach the limit we fail and throw an exception */
|
|
| 580 | + raiseAsync(cap, tso,
|
|
| 581 | + (StgClosure *)blockedOnBadFD_closure
|
|
| 582 | + /*TODO: use ioopResourcesExhausted_closure */,
|
|
| 583 | + false, NULL);
|
|
| 584 | + return false;
|
|
| 585 | + // TODO: review this
|
|
| 586 | + // TODO: current return value is for memory alloc failure, not for
|
|
| 587 | + // other failures. Need error result separate from alloc.
|
|
| 588 | + }
|
|
| 589 | + |
|
| 590 | + if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
|
|
| 591 | + bool ok = enlargeTables(iomgr);
|
|
| 592 | + if (RTS_UNLIKELY(!ok))
|
|
| 593 | + return false;
|
|
| 594 | + }
|
|
| 595 | + |
|
| 596 | + int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
|
|
| 597 | + |
|
| 598 | + aiop->capno = cap->no;
|
|
| 599 | + aiop->index = index;
|
|
| 600 | + |
|
| 601 | + /* Allocate an SQE on the ring */
|
|
| 602 | + struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
|
|
| 603 | + ASSERT(sqe); /* Otherwise we counted wrong */
|
|
| 604 | + iomgr->n_submitted_b++;
|
|
| 605 | + iomgr->n_prepared_b++;
|
|
| 606 | + io_uring_sqe_set_data64(sqe, index);
|
|
| 607 | + *psqe = sqe;
|
|
| 608 | + return 0;
|
|
| 609 | +}
|
|
| 610 | + |
|
| 611 | +/* The common epilogue for all async non-blocking I/O operations.
|
|
| 612 | + */
|
|
| 613 | +static int epilogueAsyncNonBlockingIOOp(CapIOManager *iomgr, StgTSO *tso,
|
|
| 614 | + StgAsyncIOOp *aiop, bool tso_block) {
|
|
| 615 | + if (tso_block) {
|
|
| 616 | + tso->why_blocked = BlockedOnIOSubmission;
|
|
| 617 | + tso->block_info.aiop = aiop;
|
|
| 618 | + return -1;
|
|
| 619 | + } else if (io_uring_sq_space_left(iomgr->uring) == 0) {
|
|
| 620 | + return -1;
|
|
| 621 | + } else {
|
|
| 622 | + return 0;
|
|
| 623 | + }
|
|
| 624 | +}
|
|
| 625 | + |
|
| 626 | +/* Common epilogue for all async blocking I/O operations.
|
|
| 627 | + */
|
|
| 628 | +static int epilogueAsyncBlockingIOOp(CapIOManager *iomgr) {
|
|
| 629 | + return io_uring_sq_space_left(iomgr->uring) == 0 ? -1 : 0;
|
|
| 630 | +}
|
|
| 631 | + |
|
| 632 | +/******************************************************************************
|
|
| 633 | + * Non-blocking I/O operations. This includes read/write on files (not sockets).
|
|
| 634 | + *
|
|
| 635 | + * The code is organised as common I/O preparation functions and then individual
|
|
| 636 | + * primops (sync and async). They also rely on the common prologue and epilogue
|
|
| 637 | + * functions above.
|
|
| 638 | + */
|
|
| 639 | + |
|
| 640 | +static int prepareIOReadWrite(CapIOManager *iomgr, StgTSO *tso, bool *tso_block,
|
|
| 641 | + StgAsyncIOOp *aiop, IOReadOrWrite rw, int fd,
|
|
| 642 | + StgClosure *live, void *buf, size_t len,
|
|
| 643 | + off_t off) {
|
|
| 644 | + struct io_uring_sqe *sqe;
|
|
| 645 | + int fail = prologueNonBlockingIOOp(iomgr, tso, aiop, &sqe, tso_block);
|
|
| 646 | + if (RTS_UNLIKELY(fail))
|
|
| 647 | + return fail;
|
|
| 648 | + |
|
| 649 | + aiop->live = live;
|
|
| 650 | + if (rw == IORead) {
|
|
| 651 | + io_uring_prep_read(sqe, fd, buf, len, off);
|
|
| 652 | + } else {
|
|
| 653 | + io_uring_prep_write(sqe, fd, buf, len, off);
|
|
| 654 | + }
|
|
| 655 | + return 0;
|
|
| 656 | +}
|
|
| 657 | + |
|
| 658 | +int asyncIOReadWriteIOURing(CapIOManager *iomgr, StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 659 | + IOReadOrWrite rw, int fd, StgClosure *live,
|
|
| 660 | + void *buf, size_t len, off_t off) {
|
|
| 661 | + bool tso_block;
|
|
| 662 | + int fail = prepareIOReadWrite(iomgr, tso, &tso_block, aiop, rw, fd, live, buf,
|
|
| 663 | + len, off);
|
|
| 664 | + if (RTS_UNLIKELY(fail))
|
|
| 665 | + return fail;
|
|
| 666 | + |
|
| 667 | + return epilogueAsyncNonBlockingIOOp(iomgr, tso, aiop, tso_block);
|
|
| 668 | +}
|
|
| 669 | + |
|
| 670 | +int syncIOReadWriteIOURing(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
|
|
| 671 | + int fd, StgClosure *live, void *buf, size_t len,
|
|
| 672 | + off_t off) {
|
|
| 673 | + StgAsyncIOOp *aiop;
|
|
| 674 | + int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 675 | + int fail = prologueSyncIOOp(iomgr, tso, why_blocked, &aiop);
|
|
| 676 | + if (RTS_UNLIKELY(fail))
|
|
| 677 | + return fail;
|
|
| 678 | + |
|
| 679 | + bool unused;
|
|
| 680 | + fail =
|
|
| 681 | + prepareIOReadWrite(iomgr, tso, &unused, aiop, rw, fd, live, buf, len, off);
|
|
| 682 | + if (RTS_UNLIKELY(fail))
|
|
| 683 | + return fail;
|
|
| 684 | + return 0;
|
|
| 685 | +}
|
|
| 686 | + |
|
| 687 | +/******************************************************************************
|
|
| 688 | + * Blocking I/O operations. This includes waiting for I/O readiness on sockets,
|
|
| 689 | + * pipes etc.
|
|
| 690 | + *
|
|
| 691 | + * The code is organised as common I/O preparation functions and then individual
|
|
| 692 | + * primops (sync and async). They also rely on the common prologue and epilogue
|
|
| 693 | + * functions above.
|
|
| 694 | + */
|
|
| 695 | + |
|
| 696 | +static void prepareIOWaitReady(StgAsyncIOOp *aiop, struct io_uring_sqe *sqe,
|
|
| 697 | + IOReadOrWrite rw, int fd) {
|
|
| 698 | + aiop->live = &stg_ASYNCIO_LIVE0_closure;
|
|
| 699 | + io_uring_prep_poll_add(sqe, fd, rw == IORead ? POLLIN : POLLOUT);
|
|
| 700 | +}
|
|
| 701 | + |
|
| 702 | +bool asyncIOWaitReadyIOURing(CapIOManager *iomgr, StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 703 | + IOReadOrWrite rw, int fd) {
|
|
| 704 | + struct io_uring_sqe *sqe;
|
|
| 705 | + bool ok = prologueBlockingIOOp(iomgr, tso, aiop, &sqe);
|
|
| 706 | + if (RTS_UNLIKELY(!ok))
|
|
| 707 | + return false;
|
|
| 708 | + |
|
| 709 | + prepareIOWaitReady(aiop, sqe, rw, fd);
|
|
| 710 | + |
|
| 711 | + return epilogueAsyncBlockingIOOp(iomgr);
|
|
| 712 | +}
|
|
| 713 | + |
|
| 714 | +bool syncIOWaitReadyIOURing(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
|
|
| 715 | + HsInt fd) {
|
|
| 716 | + StgAsyncIOOp *aiop;
|
|
| 717 | + int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 718 | + bool ok = prologueSyncIOOp(iomgr, tso, why_blocked, &aiop);
|
|
| 719 | + if (RTS_UNLIKELY(!ok))
|
|
| 720 | + return false;
|
|
| 721 | + |
|
| 722 | + struct io_uring_sqe *sqe;
|
|
| 723 | + ok = prologueBlockingIOOp(iomgr, tso, aiop, &sqe);
|
|
| 724 | + if (RTS_UNLIKELY(!ok))
|
|
| 725 | + return false;
|
|
| 726 | + |
|
| 727 | + prepareIOWaitReady(aiop, sqe, rw, fd);
|
|
| 728 | + |
|
| 729 | + return 0;
|
|
| 730 | +}
|
|
| 731 | + |
|
| 732 | +/******************************************************************************
|
|
| 733 | + * Actions to cancel outstanding I/O operations. Also support cancelling any
|
|
| 734 | + * outstanding I/O on an fd prior to it being closed.
|
|
| 735 | + *
|
|
| 736 | + * This covers both synchronous and asynchronous operations.
|
|
| 737 | + */
|
|
| 738 | + |
|
| 739 | +static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop);
|
|
| 740 | + |
|
| 741 | +void syncIOCancelIOURing(CapIOManager *iomgr, StgTSO *tso) {
|
|
| 742 | + StgAsyncIOOp *aiop = tso->block_info.aiop;
|
|
| 743 | + ASSERT(aiop->notify_type == NotifyTSO);
|
|
| 744 | + ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
|
|
| 745 | + ioCancel(iomgr, aiop);
|
|
| 746 | + /* We cannot use the normal notifyIOCompletion here. We are in the context
|
|
| 747 | + * of throwTo, interrupting a thread blocked on IO via an async exception.
|
|
| 748 | + * We don't put the TSO back on the run queue or change the why_blocked
|
|
| 749 | + * status, as that is done by removeFromQueues (in the throwTo* functions).
|
|
| 750 | + */
|
|
| 751 | + tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 752 | + |
|
| 753 | + // TODO: Synchronous cancellation from throwTo seems to be pretty keen for
|
|
| 754 | + // the thread to be unblocked immediately so it can start unwinding the
|
|
| 755 | + // stack. Perhaps it is ok to continue the cancellation in the background.
|
|
| 756 | + // But if so then we will need to adjust the notify type to be none /
|
|
| 757 | + // cancelled!
|
|
| 758 | + /* Cancelling thread-synchronous I/O happens from throwTo, which is very
|
|
| 759 | + * keen for the thread to be unblocked immediately so it can unwind the
|
|
| 760 | + * stack and schedule the thread to run an exception handler. This demand
|
|
| 761 | + * to be synchronous is a bit tricky to arrange because cancelling the I/O
|
|
| 762 | + * operation the thread is blocked on is potentially asynchronous.
|
|
| 763 | + */
|
|
| 764 | + aiop->notify_type = NotifyNone;
|
|
| 765 | + aiop->notify.tso = END_TSO_QUEUE;
|
|
| 766 | +}
|
|
| 767 | + |
|
| 768 | +void asyncIOCancelIOURing(CapIOManager *iomgr, StgAsyncIOOp *aiop) {
|
|
| 769 | + /* We can reliably determine if the aiop is still in progress by checking
|
|
| 770 | + * if the aiop_table still points to this aiop object. This is reliable
|
|
| 771 | + * because each aiop is GC heap allocated, so cannot be recycled until it
|
|
| 772 | + * is no longer retained by the application.
|
|
| 773 | + */
|
|
| 774 | + ASSERT(aiop->notify_type != NotifyTSO);
|
|
| 775 | + if (indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop) {
|
|
| 776 | + ioCancel(iomgr, aiop);
|
|
| 777 | + notifyIOCompletion(iomgr, aiop);
|
|
| 778 | + }
|
|
| 779 | +}
|
|
| 780 | + |
|
| 781 | +static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop) {
|
|
| 782 | + barf("URing.c:ioCancel:TODO");
|
|
| 783 | + |
|
| 784 | + int ix = aiop->index;
|
|
| 785 | + struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
|
|
| 786 | + // TODO: return status to indicate if we need to return to the scheduler
|
|
| 787 | + // to flush a full submission queue
|
|
| 788 | + |
|
| 789 | + /* io_uring lets us include one word into submission queue entries (SQEs),
|
|
| 790 | + * which come back in the corresponding completion queue entry (CQE). We
|
|
| 791 | + * use this feature to identify the aiop so that we will be able to
|
|
| 792 | + * process the completion properly, e.g. waking up the right thread.
|
|
| 793 | + * We can't use a direct pointer to the aiop because the aiops are heap
|
|
| 794 | + * allocated and GC pointers are not stable. We use the index in the
|
|
| 795 | + * ClosureTable, because this is stable. Indeed the raison d'être of the
|
|
| 796 | + * ClosureTable is to provide stable pointers for thus purpose.
|
|
| 797 | + //TODO: the above is a helpful comment but it belongs elsewhere, e.g.
|
|
| 798 | + //in the intro.
|
|
| 799 | + */
|
|
| 800 | + uint64_t sqe_data = ix;
|
|
| 801 | + |
|
| 802 | + /* Although IORING_OP_POLL_ADD has a special separate cancellation using
|
|
| 803 | + * IORING_OP_POLL_REMOVE, apparently it can also be cancelled using the
|
|
| 804 | + * generic IORING_OP_ASYNC_CANCEL, which is good so we don't need to
|
|
| 805 | + * distinguish.
|
|
| 806 | + */
|
|
| 807 | + io_uring_prep_cancel64(sqe, sqe_data, 0 /*flags*/);
|
|
| 808 | + // TODO: if we use other tags in the sqe_data we'll need to reconstruct
|
|
| 809 | + // them here so we can find the right item. e.g. if we use AIOP_TAG_OP_NB
|
|
| 810 | + // we'd need to distinguish in the aiop->flags for example.
|
|
| 811 | + |
|
| 812 | + /* Cancelling is itself a new uring I/O operation which will have a
|
|
| 813 | + * corresponding completion. Set a high bit to mark this as a cancel
|
|
| 814 | + * operation, but still knowing the index of the original operation.
|
|
| 815 | + */
|
|
| 816 | + io_uring_sqe_set_data64(sqe, sqe_data | AIOP_TAG_CANCEL);
|
|
| 817 | + |
|
| 818 | + // TODO: verify for IORING_OP_POLL_ADD, if we do a successful
|
|
| 819 | + // IORING_OP_POLL_REMOVE then which completions do we get?
|
|
| 820 | + // Do we get a completion for the IORING_OP_POLL_ADD, and if so with
|
|
| 821 | + // what result? Or does a successful IORING_OP_POLL_REMOVE mean we
|
|
| 822 | + // only get a completion for the remove and not the add?
|
|
| 823 | + |
|
| 824 | + // TODO: verify similar for normal I/O cancel, e.g. a read on a pipe.
|
|
| 825 | + // Which completions do we get if we cancel?
|
|
| 826 | + |
|
| 827 | + // TODO: is it ok cancel I/O asynchronously here? Do we need to submit the
|
|
| 828 | + // cancel op? The cancel op will complete synchronously but the cancellation
|
|
| 829 | + // may only complete later. This might confuse resource cleanup, e.g. because
|
|
| 830 | + // a file will not get closed until the cancel finishes.
|
|
| 831 | +}
|
|
| 832 | + |
|
| 833 | +/* TODO: need to add support for closing properly.
|
|
| 834 | + * Unfortunately, uring's behaviour for closing a fd when there are outstanding
|
|
| 835 | + * poll (or other async I/O) operations on that fd is unhelpful. The poll
|
|
| 836 | + * operation itself keeps a reference to the file open. Thus the close will
|
|
| 837 | + * not in fact interrupt and cancel the poll.
|
|
| 838 | + * So the I/O manager needs to be notified of fd close, so that we can do
|
|
| 839 | + * something. Fortunately we can use io_uring_prep_cancel_fd to cancel all
|
|
| 840 | + * operations on an fd.
|
|
| 841 | + *
|
|
| 842 | + * I think cancelled ops _do_ generate CQEs. So we should be able to do the
|
|
| 843 | + * appropriate notifications by waiting for the original CQEs. We should
|
|
| 844 | + * probably issue the cancellation
|
|
| 845 | + *
|
|
| 846 | + */
|
|
| 847 | + |
|
| 848 | +/******************************************************************************
|
|
| 849 | + * The functions called from the scheduler to poll or wait for pending I/O,
|
|
| 850 | + * and process any I/O completions.
|
|
| 851 | + */
|
|
| 852 | + |
|
| 853 | +bool anyPendingTimeoutsOrIOIOURing(CapIOManager *iomgr) {
|
|
| 854 | + return !isEmptyTimeoutQueue(iomgr->timeout_queue) ||
|
|
| 855 | + !isEmptyClosureTable(&iomgr->aiop_table);
|
|
| 856 | +}
|
|
| 857 | + |
|
| 858 | +static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) {
|
|
| 859 | + switch (aiop->notify_type) {
|
|
| 860 | + case NotifyTSO: {
|
|
| 861 | + if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
|
|
| 862 | + /* The fd is invalid: raise an IOError exception in the blocked
|
|
| 863 | + * thread. (See bug #4934 for what happens without this.)
|
|
| 864 | + */
|
|
| 865 | + StgTSO *tso = aiop->notify.tso;
|
|
| 866 | + debugTrace(DEBUG_iomanager,
|
|
| 867 | + "Raising exception in thread %" FMT_StgThreadID
|
|
| 868 | + " blocked on an invalid fd",
|
|
| 869 | + tso->id);
|
|
| 870 | + raiseAsync(iomgr->cap, tso, (StgClosure *)blockedOnBadFD_closure, false, NULL);
|
|
| 871 | + break;
|
|
| 872 | + } else {
|
|
| 873 | + /* We should be guaranteed that the tso is still on the same
|
|
| 874 | + * cap because the tso was not on the run queue of any cap and
|
|
| 875 | + * so is not subject to thread migration.
|
|
| 876 | + */
|
|
| 877 | + StgTSO *tso = aiop->notify.tso;
|
|
| 878 | + tso->why_blocked = NotBlocked;
|
|
| 879 | + tso->_link = END_TSO_QUEUE;
|
|
| 880 | + pushOnRunQueue(iomgr->cap, tso);
|
|
| 881 | + }
|
|
| 882 | + break;
|
|
| 883 | + }
|
|
| 884 | + case NotifyMVar:
|
|
| 885 | + performTryPutMVar(iomgr->cap, aiop->notify.mvar, Unit_closure);
|
|
| 886 | + break;
|
|
| 887 | + |
|
| 888 | + case NotifyTVar:
|
|
| 889 | + barf("uring iomgr: TVar notification not yet supported");
|
|
| 890 | + break;
|
|
| 891 | + }
|
|
| 892 | +}
|
|
| 893 | + |
|
| 894 | +/* Process all the I/O completions that are currently available without
|
|
| 895 | + * blocking.
|
|
| 896 | + *
|
|
| 897 | + * This will correctly deal with completion queue overflow: if the completions
|
|
| 898 | + * queue is empty but has the overflow bit set then io_uring_peek_batch_cqe
|
|
| 899 | + * will do another non-blocking uring enter to refill the completion queue.
|
|
| 900 | + */
|
|
| 901 | +static void processIOCompletions(CapIOManager *iomgr) {
|
|
| 902 | + struct io_uring *uring = iomgr->uring;
|
|
| 903 | + while (1) {
|
|
| 904 | + unsigned head, count = 0;
|
|
| 905 | + struct io_uring_cqe *cqe;
|
|
| 906 | + io_uring_for_each_cqe(uring, head, cqe) {
|
|
| 907 | + uint64_t cqe_data = io_uring_cqe_get_data64(cqe);
|
|
| 908 | + if (RTS_UNLIKELY(cqe_data & AIOP_TAG_CANCEL)) {
|
|
| 909 | + // This is a CQE for a cancellation. After posting a cancellation SQE
|
|
| 910 | + // then we expect to get _both_ a cancellation CQE and a CQE for the
|
|
| 911 | + // original operation that was the target of cancellation. This means
|
|
| 912 | + // that (provided it's not an error) we can ignore the cancellation SQE
|
|
| 913 | + // and just process the normal SQE for the target operation.
|
|
| 914 | + //
|
|
| 915 | + // The target operation will either be cancelled successfully
|
|
| 916 | + // immediately (in which case the cancellation cqe->res == 0) or if the
|
|
| 917 | + // target operation is in progress and cannot be cancelled, then we'll
|
|
| 918 | + // get cqe->res == -EALREADY for the cancellation the result of the
|
|
| 919 | + // operation (which may still be an interrupted outcome. If the
|
|
| 920 | + // cancellation fails, we
|
|
| 921 | + if (cqe->res != 0 && cqe->res != -EALREADY)
|
|
| 922 | + sysErrorBelch("uring:processIOCompletions: unknown cancel CQE result");
|
|
| 923 | + } else {
|
|
| 924 | + int ix = cqe_data;
|
|
| 925 | + StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, ix);
|
|
| 926 | + removeClosureTable(iomgr->cap, &iomgr->aiop_table, ix);
|
|
| 927 | + aiop->result = cqe->res;
|
|
| 928 | + // TODO: if we use these tags in the sqe_data we'll need to reconstruct
|
|
| 929 | + // them for cancel so we can find the right item. e.g. if we use
|
|
| 930 | + // AIOP_TAG_OP_NB we'd need to distinguish in the aiop->flags for
|
|
| 931 | + // example.
|
|
| 932 | + if (cqe_data & AIOP_TAG_OP_NB) {
|
|
| 933 | + iomgr->n_inflight_nb--;
|
|
| 934 | + iomgr->n_submitted_nb--;
|
|
| 935 | + } else {
|
|
| 936 | + iomgr->n_inflight_b--;
|
|
| 937 | + iomgr->n_submitted_b--;
|
|
| 938 | + }
|
|
| 939 | + notifyIOCompletion(iomgr, aiop);
|
|
| 940 | + }
|
|
| 941 | + count++;
|
|
| 942 | + }
|
|
| 943 | + io_uring_cq_advance(uring, count);
|
|
| 944 | + if (RTS_UNLIKELY(io_uring_cq_has_overflow(uring))) {
|
|
| 945 | + if (io_uring_get_events(uring) < 0) {
|
|
| 946 | + sysErrorBelch("io_uring_enter");
|
|
| 947 | + stg_exit(EXIT_FAILURE);
|
|
| 948 | + }
|
|
| 949 | + continue;
|
|
| 950 | + } else {
|
|
| 951 | + break;
|
|
| 952 | + }
|
|
| 953 | + }
|
|
| 954 | +}
|
|
| 955 | + |
|
| 956 | +/* Check invariants that must hold on entry to and exit from the scheduler.
|
|
| 957 | + * Used before/after {poll,await}CompletedTimeoutsOrIOURing which are called
|
|
| 958 | + * from the scheduler.
|
|
| 959 | + */
|
|
| 960 | +static void assertURingSchedulerInvariants(CapIOManager *iomgr) {
|
|
| 961 | + struct io_uring *uring = iomgr->uring;
|
|
| 962 | + |
|
| 963 | + // That our tracking counters are consistent.
|
|
| 964 | + ASSERT(iomgr->n_submitted_b == iomgr->n_prepared_b + iomgr->n_inflight_b);
|
|
| 965 | + ASSERT(iomgr->n_submitted_nb ==
|
|
| 966 | + iomgr->n_prepared_nb + iomgr->n_overflow_nb + iomgr->n_inflight_nb);
|
|
| 967 | + |
|
| 968 | + // That we are within limits
|
|
| 969 | + ASSERT(iomgr->n_inflight_b <= iomgr->limit_inflight_b);
|
|
| 970 | + ASSERT(iomgr->n_inflight_nb <= iomgr->limit_inflight_nb);
|
|
| 971 | + |
|
| 972 | + // That we correctly track the submission queue size.
|
|
| 973 | + ASSERT((int)io_uring_sq_ready(uring) ==
|
|
| 974 | + iomgr->n_prepared_b + iomgr->n_prepared_nb);
|
|
| 975 | + |
|
| 976 | + // That our overflow queue is consistent with the overflow counter.
|
|
| 977 | + ASSERT(iomgr->n_overflow_nb > 0
|
|
| 978 | + ? iomgr->overflow_sqe_q_hd == NULL &&
|
|
| 979 | + iomgr->overflow_sqe_q_tl == NULL &&
|
|
| 980 | + iomgr->overflow_tso_q_hd == END_TSO_QUEUE &&
|
|
| 981 | + iomgr->overflow_tso_q_tl == END_TSO_QUEUE
|
|
| 982 | + : iomgr->n_overflow_nb == 0 && iomgr->overflow_sqe_q_hd != NULL &&
|
|
| 983 | + iomgr->overflow_sqe_q_tl != NULL &&
|
|
| 984 | + iomgr->overflow_tso_q_hd != END_TSO_QUEUE &&
|
|
| 985 | + iomgr->overflow_tso_q_tl != END_TSO_QUEUE);
|
|
| 986 | +}
|
|
| 987 | + |
|
| 988 | +/* If there are any completed I/O operations or expired timers, process the
|
|
| 989 | + * completions as appropriate. If there are none, return without waiting.
|
|
| 990 | + *
|
|
| 991 | + * This is the non-blocking variant. See awaitCompletedTimeoutsOrIOURing
|
|
| 992 | + * for the potentially-blocking variant.
|
|
| 993 | + */
|
|
| 994 | +void pollCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr) {
|
|
| 995 | + struct io_uring *uring = iomgr->uring;
|
|
| 996 | + |
|
| 997 | + assertURingSchedulerInvariants(iomgr);
|
|
| 998 | + |
|
| 999 | + /* Process timeouts, if any, but don't immediately return to the scheduler,
|
|
| 1000 | + * since we should submit I/O and reap any completions too.
|
|
| 1001 | + */
|
|
| 1002 | + if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
|
|
| 1003 | + Time now = getProcessElapsedTime();
|
|
| 1004 | + processTimeoutCompletions(iomgr, now);
|
|
| 1005 | + }
|
|
| 1006 | + |
|
| 1007 | + /* Submit I/O if needed */
|
|
| 1008 | + if (io_uring_sq_ready(uring)) {
|
|
| 1009 | + int res = io_uring_submit_and_get_events(uring);
|
|
| 1010 | + |
|
| 1011 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 1012 | + if (res == -EBUSY) {
|
|
| 1013 | + /* This is an odd one. According to the doc:
|
|
| 1014 | + * If the IORING_FEAT_NODROP feature flag is set, then EBUSY
|
|
| 1015 | + * will be returned if there were overflow entries,
|
|
| 1016 | + * IORING_ENTER_GETEVENTS flag is set and not all of the
|
|
| 1017 | + * overflow entries were able to be flushed to the CQ ring.
|
|
| 1018 | + *
|
|
| 1019 | + * So it's not really an error at all. It just means we will
|
|
| 1020 | + * have to do multiple iterations in processIOCompletions()
|
|
| 1021 | + * to collect all the completions.
|
|
| 1022 | + *
|
|
| 1023 | + * Thus EBUSY should imply that there are entries in the CQ.
|
|
| 1024 | + */
|
|
| 1025 | + ASSERT(io_uring_cq_ready(uring) > 0);
|
|
| 1026 | + }
|
|
| 1027 | + } else {
|
|
| 1028 | + ASSERT(res == iomgr->n_prepared_b + iomgr->n_prepared_nb);
|
|
| 1029 | + /* We're using IORING_SETUP_SUBMIT_ALL so we should expect to have
|
|
| 1030 | + * all of them submitted, or an error.
|
|
| 1031 | + * https://github.com/axboe/liburing/issues/186
|
|
| 1032 | + * Alternatively, we could loop and submit the remainder.
|
|
| 1033 | + */
|
|
| 1034 | + iomgr->n_inflight_b += iomgr->n_prepared_b;
|
|
| 1035 | + iomgr->n_inflight_nb += iomgr->n_prepared_nb;
|
|
| 1036 | + iomgr->n_prepared_b = 0;
|
|
| 1037 | + iomgr->n_prepared_nb = 0;
|
|
| 1038 | + }
|
|
| 1039 | + }
|
|
| 1040 | + |
|
| 1041 | + if (io_uring_cq_ready(uring)) {
|
|
| 1042 | + processIOCompletions(iomgr);
|
|
| 1043 | + }
|
|
| 1044 | + // TODO: now we need to check if we have any items in our overflow queue
|
|
| 1045 | + // and if so, we need to copy some of those into the SQ and submit them.
|
|
| 1046 | + // copy in up to either the SQ limit or in-flight limit.
|
|
| 1047 | + |
|
| 1048 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1049 | +}
|
|
| 1050 | + |
|
| 1051 | +/* If there are any completed I/O operations or expired timers, process the
|
|
| 1052 | + * completions as appropriate. If there are none, wait until I/O or a timer
|
|
| 1053 | + * does complete (or we get a signal with a handler) and process the
|
|
| 1054 | + * completions as appropriate.
|
|
| 1055 | + *
|
|
| 1056 | + * This is the potentially-blocking variant. See pollCompletedTimeoutsOrIOURing
|
|
| 1057 | + * for the non-blocking variant.
|
|
| 1058 | + */
|
|
| 1059 | +bool awaitCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr) {
|
|
| 1060 | + struct io_uring *uring = iomgr->uring;
|
|
| 1061 | + |
|
| 1062 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1063 | + |
|
| 1064 | + do {
|
|
| 1065 | + |
|
| 1066 | + /* We're being asked (by the scheduler) to block if there's no
|
|
| 1067 | + * immediate timer or I/O completions. So there had better be
|
|
| 1068 | + * some pending I/O or pending timers, or we'd deadlock.
|
|
| 1069 | + */
|
|
| 1070 | + ASSERT(!isEmptyTimeoutQueue(iomgr->timeout_queue) ||
|
|
| 1071 | + !isEmptyClosureTable(&iomgr->aiop_table));
|
|
| 1072 | + |
|
| 1073 | + Time now = getProcessElapsedTime();
|
|
| 1074 | + processTimeoutCompletions(iomgr, now);
|
|
| 1075 | + |
|
| 1076 | + /* If we didn't wake any threads due to expiring timeouts, then we need
|
|
| 1077 | + * to wait on I/O. Or to put it another way, even if we did wake some
|
|
| 1078 | + * threads, we'll still poll (but not wait) for I/O. This is to ensure
|
|
| 1079 | + * we avoid starving threads blocked on I/O.
|
|
| 1080 | + */
|
|
| 1081 | + bool wait = emptyRunQueue(iomgr->cap);
|
|
| 1082 | + |
|
| 1083 | + /* There are four possible cases:
|
|
| 1084 | + * 1. non-blocking check for I/O completion with I/O submission
|
|
| 1085 | + * 2. non-blocking check for I/O completion with no I/O submission
|
|
| 1086 | + * 3. blocking wait for I/O completion with a timeout
|
|
| 1087 | + * 4. blocking wait for I/O completion without a timeout
|
|
| 1088 | + *
|
|
| 1089 | + * If we woke any threads due to timeouts we're in the first or second
|
|
| 1090 | + * case.
|
|
| 1091 | + *
|
|
| 1092 | + * Otherwise we're in one of the blocking cases. We will use a timeout
|
|
| 1093 | + * if the timeout queue is non-empty.
|
|
| 1094 | + */
|
|
| 1095 | + |
|
| 1096 | + int res;
|
|
| 1097 | + if (!wait && io_uring_sq_ready(uring)) {
|
|
| 1098 | + /* Case 1 (as above) */
|
|
| 1099 | + res = io_uring_submit_and_get_events(uring);
|
|
| 1100 | + } else if (!wait) {
|
|
| 1101 | + /* Case 2 (as above) */
|
|
| 1102 | + res = io_uring_get_events(uring);
|
|
| 1103 | + } else {
|
|
| 1104 | + struct timespec tv;
|
|
| 1105 | + if (timeoutInNanoseconds(iomgr, true, now, &tv)) {
|
|
| 1106 | + /* Case 3 (as above) */
|
|
| 1107 | + /* struct timespec and struct __kernel_timespec are compatible
|
|
| 1108 | + * but not exactly the same. Sigh. */
|
|
| 1109 | + struct __kernel_timespec ts = {.tv_sec = tv.tv_sec,
|
|
| 1110 | + .tv_nsec = tv.tv_nsec};
|
|
| 1111 | + struct io_uring_cqe *cqe_unused;
|
|
| 1112 | + res =
|
|
| 1113 | + io_uring_submit_and_wait_timeout(uring, &cqe_unused, 1, &ts, NULL);
|
|
| 1114 | + } else {
|
|
| 1115 | + /* Case 4 (as above) */
|
|
| 1116 | + res = io_uring_submit_and_wait(uring, 1);
|
|
| 1117 | + }
|
|
| 1118 | + }
|
|
| 1119 | + |
|
| 1120 | + if (res >= 0) {
|
|
| 1121 | + processIOCompletions(iomgr);
|
|
| 1122 | + } else if (errno == EINTR) {
|
|
| 1123 | + |
|
| 1124 | + } else if (errno == EBUSY || errno == EAGAIN) {
|
|
| 1125 | + |
|
| 1126 | + } else {
|
|
| 1127 | + sysErrorBelch("io_uring_enter");
|
|
| 1128 | + stg_exit(EXIT_FAILURE);
|
|
| 1129 | + }
|
|
| 1130 | + } while (emptyRunQueue(iomgr->cap) &&
|
|
| 1131 | + (!isEmptyClosureTable(&iomgr->aiop_table) ||
|
|
| 1132 | + !isEmptyTimeoutQueue(iomgr->timeout_queue)) &&
|
|
| 1133 | + getSchedState() == SCHED_RUNNING);
|
|
| 1134 | + |
|
| 1135 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1136 | + |
|
| 1137 | + return true; // TODO return false if interrupted
|
|
| 1138 | +}
|
|
| 1139 | + |
|
| 1140 | +void interruptIOManagerIOURing(CapIOManager *iomgr) {
|
|
| 1141 | + barf("interruptIOManagerIOURing:TODO");
|
|
| 1142 | + |
|
| 1143 | + // Here are 2 possible ways to implement interrupt:
|
|
| 1144 | + //
|
|
| 1145 | + // * On initialization, create a new FD with newFdWakeup() and submit a
|
|
| 1146 | + // recurring POLL SQE. Then on interrupt, call sendFdWakeup() to foce a CQE
|
|
| 1147 | + // to be generated.
|
|
| 1148 | + // * Submit a NOP (see io_uring_prep_nop()) SQE. This should produce a trivial
|
|
| 1149 | + // NOP CQE. The issue with this is that the SQE may already be full, which
|
|
| 1150 | + // requires some solution.
|
|
| 1151 | +}
|
|
| 1152 | + |
|
| 1153 | +/******************************************************************************
|
|
| 1154 | + * Local helper utilities
|
|
| 1155 | + */
|
|
| 1156 | + |
|
| 1157 | +static bool enlargeTables(CapIOManager *iomgr) {
|
|
| 1158 | + int oldcapacity = capacityClosureTable(&iomgr->aiop_table);
|
|
| 1159 | + int newcapacity = (oldcapacity == 0) ? 1 : (oldcapacity * 2);
|
|
| 1160 | + return enlargeClosureTable(iomgr->cap, &iomgr->aiop_table, newcapacity);
|
|
| 1161 | +}
|
|
| 1162 | + |
|
| 1163 | +/*
|
|
| 1164 | + */
|
|
| 1165 | +static void enqueueOverflowQueue(CapIOManager *iomgr,
|
|
| 1166 | + StgTSO *tso, struct io_uring_sqe *sqe) {
|
|
| 1167 | + /* Append the TSO to the tail of the overflow queue of TSOs. */
|
|
| 1168 | + ASSERT(tso->_link == END_TSO_QUEUE);
|
|
| 1169 | + if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
|
|
| 1170 | + iomgr->overflow_tso_q_hd = tso;
|
|
| 1171 | + } else {
|
|
| 1172 | + setTSOLink(iomgr->cap, iomgr->overflow_tso_q_tl, tso);
|
|
| 1173 | + }
|
|
| 1174 | + iomgr->overflow_tso_q_tl = tso;
|
|
| 1175 | + |
|
| 1176 | + /* And append the SQE to the tail of the overflow queue of SQEs. */
|
|
| 1177 | + struct overflow_sqe_q_t *entry;
|
|
| 1178 | + entry = stgMallocBytes(sizeof(struct overflow_sqe_q_t), "uring iomgr");
|
|
| 1179 | + *entry = (struct overflow_sqe_q_t){.sqe = sqe,
|
|
| 1180 | + .next = NULL,
|
|
| 1181 | +#if defined(DEBUG)
|
|
| 1182 | + .tid = tso->id
|
|
| 1183 | +#endif
|
|
| 1184 | + };
|
|
| 1185 | + if (iomgr->overflow_sqe_q_hd == NULL) {
|
|
| 1186 | + iomgr->overflow_sqe_q_hd = entry;
|
|
| 1187 | + } else {
|
|
| 1188 | + iomgr->overflow_sqe_q_tl->next = entry;
|
|
| 1189 | + }
|
|
| 1190 | + iomgr->overflow_sqe_q_tl = entry;
|
|
| 1191 | +}
|
|
| 1192 | + |
|
| 1193 | +static void dequeueOverflowQueue(CapIOManager *iomgr, StgTSO **ptso,
|
|
| 1194 | + struct io_uring_sqe **psqe) {
|
|
| 1195 | + /* Remove the TSO and SQE from the head of their respective queues */
|
|
| 1196 | + StgTSO *tso = iomgr->overflow_tso_q_hd;
|
|
| 1197 | + struct overflow_sqe_q_t *entry = iomgr->overflow_sqe_q_hd;
|
|
| 1198 | + |
|
| 1199 | + if (tso == END_TSO_QUEUE) {
|
|
| 1200 | + // TODO: decide if we need this or if we should assume the queue is
|
|
| 1201 | + // non-empty
|
|
| 1202 | + ASSERT(entry == NULL);
|
|
| 1203 | + *ptso = END_TSO_QUEUE;
|
|
| 1204 | + *psqe = NULL;
|
|
| 1205 | + } else {
|
|
| 1206 | + iomgr->overflow_tso_q_hd = tso->_link;
|
|
| 1207 | + RELAXED_STORE(&tso->_link, END_TSO_QUEUE);
|
|
| 1208 | + if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
|
|
| 1209 | + iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
|
|
| 1210 | + }
|
|
| 1211 | + |
|
| 1212 | + iomgr->overflow_sqe_q_hd = entry->next;
|
|
| 1213 | + if (iomgr->overflow_sqe_q_hd == NULL) {
|
|
| 1214 | + iomgr->overflow_sqe_q_tl = NULL;
|
|
| 1215 | + }
|
|
| 1216 | + |
|
| 1217 | + *ptso = tso;
|
|
| 1218 | + *psqe = entry->sqe;
|
|
| 1219 | + }
|
|
| 1220 | +}
|
|
| 1221 | + |
|
| 1222 | +#endif /* IOMGR_ENABLED_IO_URING */ |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2026
|
|
| 4 | + *
|
|
| 5 | + * An I/O manager based on io_uring
|
|
| 6 | + *
|
|
| 7 | + * Prototypes for functions in IOURing.c
|
|
| 8 | + *
|
|
| 9 | + * -------------------------------------------------------------------------*/
|
|
| 10 | + |
|
| 11 | +#pragma once
|
|
| 12 | + |
|
| 13 | +#include "IOManager.h"
|
|
| 14 | +#include "Rts.h"
|
|
| 15 | + |
|
| 16 | +#include "BeginPrivate.h"
|
|
| 17 | + |
|
| 18 | +#if defined(IOMGR_ENABLED_IO_URING)
|
|
| 19 | + |
|
| 20 | +void initCapabilityIOManagerIOURing(CapIOManager *iomgr);
|
|
| 21 | +void freeCapabilityIOManagerIOURing(CapIOManager *iomgr);
|
|
| 22 | + |
|
| 23 | +/* Synchronous I/O and timer operations */
|
|
| 24 | +bool syncIOWaitReadyIOURing(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
|
|
| 25 | + HsInt fd);
|
|
| 26 | +void syncIOCancelIOURing(CapIOManager *iomgr, StgTSO *tso);
|
|
| 27 | + |
|
| 28 | +bool syncDelayTimeoutIOURing(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay);
|
|
| 29 | +void syncDelayCancelTimeoutIOURing(CapIOManager *iomgr, StgTSO *tso);
|
|
| 30 | + |
|
| 31 | +/* Scheduler operations */
|
|
| 32 | +void pollCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr);
|
|
| 33 | +bool awaitCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr);
|
|
| 34 | +void interruptIOManagerIOURing(CapIOManager *iomgr);
|
|
| 35 | + |
|
| 36 | +#endif /* IOMGR_ENABLED_IO_MANAGER */
|
|
| 37 | + |
|
| 38 | +#include "EndPrivate.h" |
|
| \ No newline at end of file |
| ... | ... | @@ -24,7 +24,7 @@ |
| 24 | 24 | /* Currently only used by the poll I/O manager, but in future may be used by
|
| 25 | 25 | several in-RTS I/O managers.
|
| 26 | 26 | */
|
| 27 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 27 | +#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_IO_URING)
|
|
| 28 | 28 | |
| 29 | 29 | bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
|
| 30 | 30 | {
|
| ... | ... | @@ -210,7 +210,8 @@ library |
| 210 | 210 | -- implicitly added by `cc`, we must explicitly add it here to ensure
|
| 211 | 211 | -- that it is ordered correctly with libpthread, since ghc-internal.cabal
|
| 212 | 212 | -- also explicitly lists libc. See #19029.
|
| 213 | - extra-libraries: c
|
|
| 213 | + -- TODO we only want to use liburing if the io manager is enabled
|
|
| 214 | + extra-libraries: c uring
|
|
| 214 | 215 | extra-libraries-static: c
|
| 215 | 216 | if flag(librt)
|
| 216 | 217 | extra-libraries: rt
|
| ... | ... | @@ -582,6 +583,7 @@ library |
| 582 | 583 | posix/FdWakeup.c
|
| 583 | 584 | posix/MIO.c
|
| 584 | 585 | posix/Poll.c
|
| 586 | + posix/IOURing.c
|
|
| 585 | 587 | posix/Select.c
|
| 586 | 588 | posix/Signals.c
|
| 587 | 589 | posix/Timeout.c
|