David Eichmann pushed to branch wip/davide/io_uring at Glasgow Haskell Compiler / GHC

Commits:

12 changed files:

Changes:

  • docs/users_guide/runtime_control.rst
    ... ... @@ -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
    

  • rts/IOManager.c
    ... ... @@ -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

  • rts/IOManager.h
    ... ... @@ -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
    

  • rts/IOManagerInternals.h
    ... ... @@ -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;
    

  • rts/configure.ac
    ... ... @@ -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
     
    

  • rts/include/rts/Constants.h
    ... ... @@ -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
    

  • rts/include/rts/Flags.h
    ... ... @@ -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] */
    

  • rts/include/rts/storage/Closures.h
    ... ... @@ -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. */
    

  • rts/posix/IOURing.c
    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, &params);
    
    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 */

  • rts/posix/IOURing.h
    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

  • rts/posix/Timeout.c
    ... ... @@ -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
     {
    

  • rts/rts.cabal
    ... ... @@ -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