Duncan Coutts pushed to branch wip/dcoutts/io-manager-uring at Glasgow Haskell Compiler / GHC
Commits:
-
102b80f6
by Duncan Coutts at 2026-08-26T11:16:05+01:00
16 changed files:
- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/StgToCmm/Prim.hs
- hadrian/src/Oracles/Flag.hs
- rts/IOManager.c
- rts/IOManager.h
- rts/IOManagerInternals.h
- rts/PrimOps.cmm
- rts/configure.ac
- rts/include/rts/Constants.h
- rts/include/rts/Flags.h
- rts/include/rts/storage/Closures.h
- rts/posix/Poll.c
- + rts/posix/URing.c
- + rts/posix/URing.h
- + rts/posix/URing.svg
- rts/rts.cabal
Changes:
| ... | ... | @@ -3185,6 +3185,128 @@ primop WaitWriteOp "waitWrite#" GenPrimOp |
| 3185 | 3185 | effect = ReadWriteEffect
|
| 3186 | 3186 | out_of_line = True
|
| 3187 | 3187 | |
| 3188 | +------------------------------------------------------------------------
|
|
| 3189 | +section "synchronous I/O operations"
|
|
| 3190 | + {These primops read up to n bytes from an open file into memory, or write up
|
|
| 3191 | + to n bytes from memory to an open file.
|
|
| 3192 | + |
|
| 3193 | + There are I\/O primops for all combinations of:
|
|
| 3194 | + |
|
| 3195 | + * read or write to open files
|
|
| 3196 | + * pinned byte array or raw pointer for the memory buffer
|
|
| 3197 | + * I/O at the current file pointer or at a given file offset
|
|
| 3198 | + |
|
| 3199 | + All of these operations are synchronous with respect to the calling
|
|
| 3200 | + thread. It is implementation defined whether these operations block just
|
|
| 3201 | + the calling thread or block all other Haskell threads running on the same
|
|
| 3202 | + capability. In practice this depends on the I\/O manager being used.
|
|
| 3203 | + |
|
| 3204 | + The result is the number of bytes transferred (if non-negative), or an
|
|
| 3205 | + error code if negative. The error code is a system error code which,
|
|
| 3206 | + depending on the platform, is either a C/Posix style errno, or a Win32 error
|
|
| 3207 | + code.
|
|
| 3208 | + |
|
| 3209 | + Note that partial\/short reads\/writes are possible. These are common with
|
|
| 3210 | + sockets and character devices, and rare with disk block devices, but they
|
|
| 3211 | + can happen and Posix and Win32 APIs say they can happen, so they must be
|
|
| 3212 | + handled.
|
|
| 3213 | + |
|
| 3214 | + The source\/destination buffer is either specified by a pointer, or by a
|
|
| 3215 | + (mutable, pinned) byte array and an offset within the array. This area is
|
|
| 3216 | + required to be at least n bytes large. This cannot be checked for the
|
|
| 3217 | + pointer variants but it is checked for the byte array variants. For the
|
|
| 3218 | + byte array variants, the array must be pinned (so that the location of the
|
|
| 3219 | + buffer is stable for the duration of the I/O operation) and this is
|
|
| 3220 | + checked.
|
|
| 3221 | + |
|
| 3222 | + The buffer must remain live for the duration of the I/O operation (until
|
|
| 3223 | + the I/O completes or the I/O is cancelled by an asynchronous exception).
|
|
| 3224 | + For the primop variants that use byte array buffers this is done
|
|
| 3225 | + automatically: the byte array is kept live for the duration of the
|
|
| 3226 | + operation. For the pointer primop variants it is the caller's
|
|
| 3227 | + responsibility to keep the buffer live for the duration of the I/O
|
|
| 3228 | + operation. For these synchronous primops, callers can rely on the guarantee
|
|
| 3229 | + that the I/O operation is complete or cancelled by the time the I/O primop
|
|
| 3230 | + returns.
|
|
| 3231 | + |
|
| 3232 | + For the primop variants that do I/O at the current file pointer, the
|
|
| 3233 | + current file pointer constitutes shared mutable state and should be treated
|
|
| 3234 | + appropriately. Concurrent reads or writes are possible, and will happen in
|
|
| 3235 | + some sequentially consistent order, but the order is not deterministic.
|
|
| 3236 | + |
|
| 3237 | + For the primop variants that do I/O at given offsets, note that this is
|
|
| 3238 | + only supported on seekable files, which in practice means disk files. There
|
|
| 3239 | + is no mutation to a shared file pointer in this case so it is safer to
|
|
| 3240 | + use concurrent reads and writes. Callers are however responsible for
|
|
| 3241 | + avoiding concurrent write operations to overlapping ranges (either
|
|
| 3242 | + write\/write or read\/write), as these would have unspecified results.
|
|
| 3243 | + }
|
|
| 3244 | +------------------------------------------------------------------------
|
|
| 3245 | + |
|
| 3246 | +primop SyncIOReadAddrOp "syncIOReadAddr#" GenPrimOp
|
|
| 3247 | + Int# -> Addr# -> Word# ->
|
|
| 3248 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3249 | + {Args: fd, buf, byte count}
|
|
| 3250 | + with
|
|
| 3251 | + effect = ReadWriteEffect
|
|
| 3252 | + out_of_line = True
|
|
| 3253 | + |
|
| 3254 | +primop SyncIOReadByteArrayOp "syncIOReadByteArray#" GenPrimOp
|
|
| 3255 | + Int# -> MutableByteArray# RealWorld -> Word# -> Word# ->
|
|
| 3256 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3257 | + {Args: fd, buf, buf offset, byte count}
|
|
| 3258 | + with
|
|
| 3259 | + effect = ReadWriteEffect
|
|
| 3260 | + out_of_line = True
|
|
| 3261 | + |
|
| 3262 | +primop SyncIOReadAddrAtOp "syncIOReadAddrAt#" GenPrimOp
|
|
| 3263 | + Int# -> Addr# -> Word# -> Int64# ->
|
|
| 3264 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3265 | + {Args: fd, buf, byte count, file offset}
|
|
| 3266 | + with
|
|
| 3267 | + effect = ReadWriteEffect
|
|
| 3268 | + out_of_line = True
|
|
| 3269 | + |
|
| 3270 | +primop SyncIOReadByteArrayAtOp "syncIOReadByteArrayAt#" GenPrimOp
|
|
| 3271 | + Int# -> MutableByteArray# RealWorld -> Word# -> Word# -> Int64# ->
|
|
| 3272 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3273 | + {Args: fd, buf, buf offset, byte count, file offset}
|
|
| 3274 | + with
|
|
| 3275 | + effect = ReadWriteEffect
|
|
| 3276 | + out_of_line = True
|
|
| 3277 | + |
|
| 3278 | +primop SyncIOWriteAddrOp "syncIOWriteAddr#" GenPrimOp
|
|
| 3279 | + Int# -> Addr# -> Word# ->
|
|
| 3280 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3281 | + {Args: fd, buf, byte count}
|
|
| 3282 | + with
|
|
| 3283 | + effect = ReadWriteEffect
|
|
| 3284 | + out_of_line = True
|
|
| 3285 | + |
|
| 3286 | +primop SyncIOWriteByteArrayOp "syncIOWriteByteArray#" GenPrimOp
|
|
| 3287 | + Int# -> ByteArray# -> Word# -> Word# ->
|
|
| 3288 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3289 | + {Args: fd, buf, buf offset, byte count}
|
|
| 3290 | + with
|
|
| 3291 | + effect = ReadWriteEffect
|
|
| 3292 | + out_of_line = True
|
|
| 3293 | + |
|
| 3294 | +primop SyncIOWriteAddrAtOp "syncIOWriteAddrAt#" GenPrimOp
|
|
| 3295 | + Int# -> Addr# -> Word# -> Int64# ->
|
|
| 3296 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3297 | + {Args: fd, buf, byte count, file offset}
|
|
| 3298 | + with
|
|
| 3299 | + effect = ReadWriteEffect
|
|
| 3300 | + out_of_line = True
|
|
| 3301 | + |
|
| 3302 | +primop SyncIOWriteByteArrayAtOp "syncIOWriteByteArrayAt#" GenPrimOp
|
|
| 3303 | + Int# -> ByteArray# -> Word# -> Word# -> Int64# ->
|
|
| 3304 | + State# RealWorld -> (# State# RealWorld, Int# #)
|
|
| 3305 | + {Args: fd, buf, buf offset, byte count, file offset}
|
|
| 3306 | + with
|
|
| 3307 | + effect = ReadWriteEffect
|
|
| 3308 | + out_of_line = True
|
|
| 3309 | + |
|
| 3188 | 3310 | ------------------------------------------------------------------------
|
| 3189 | 3311 | section "Concurrency primitives"
|
| 3190 | 3312 | ------------------------------------------------------------------------
|
| ... | ... | @@ -1714,6 +1714,14 @@ emitPrimOp cfg primop = |
| 1714 | 1714 | DelayOp -> alwaysExternal
|
| 1715 | 1715 | WaitReadOp -> alwaysExternal
|
| 1716 | 1716 | WaitWriteOp -> alwaysExternal
|
| 1717 | + SyncIOReadAddrOp -> alwaysExternal
|
|
| 1718 | + SyncIOReadByteArrayOp -> alwaysExternal
|
|
| 1719 | + SyncIOReadAddrAtOp -> alwaysExternal
|
|
| 1720 | + SyncIOReadByteArrayAtOp -> alwaysExternal
|
|
| 1721 | + SyncIOWriteAddrOp -> alwaysExternal
|
|
| 1722 | + SyncIOWriteByteArrayOp -> alwaysExternal
|
|
| 1723 | + SyncIOWriteAddrAtOp -> alwaysExternal
|
|
| 1724 | + SyncIOWriteByteArrayAtOp -> alwaysExternal
|
|
| 1717 | 1725 | ForkOp -> alwaysExternal
|
| 1718 | 1726 | ForkOnOp -> alwaysExternal
|
| 1719 | 1727 | KillThreadOp -> alwaysExternal
|
| ... | ... | @@ -37,6 +37,7 @@ data Flag = CrossCompiling |
| 37 | 37 | | UseLibdl
|
| 38 | 38 | | UseLibbfd
|
| 39 | 39 | | UseLibpthread
|
| 40 | + | UseLiburing
|
|
| 40 | 41 | | NeedLibatomic
|
| 41 | 42 | | UseGhcToolchain
|
| 42 | 43 | |
| ... | ... | @@ -61,6 +62,7 @@ flag f = do |
| 61 | 62 | UseLibdl -> "use-lib-dl"
|
| 62 | 63 | UseLibbfd -> "use-lib-bfd"
|
| 63 | 64 | UseLibpthread -> "use-lib-pthread"
|
| 65 | + UseLiburing -> "use-lib-uring"
|
|
| 64 | 66 | NeedLibatomic -> "need-libatomic"
|
| 65 | 67 | UseGhcToolchain -> "use-ghc-toolchain"
|
| 66 | 68 | value <- lookupSystemConfig key
|
| ... | ... | @@ -38,6 +38,11 @@ |
| 38 | 38 | #include "posix/Timeout.h"
|
| 39 | 39 | #endif
|
| 40 | 40 | |
| 41 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 42 | +#include "posix/URing.h"
|
|
| 43 | +#include "posix/Timeout.h"
|
|
| 44 | +#endif
|
|
| 45 | + |
|
| 41 | 46 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 42 | 47 | #include "posix/Signals.h"
|
| 43 | 48 | #include "Prelude.h"
|
| ... | ... | @@ -114,6 +119,14 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag) |
| 114 | 119 | return IOManagerAvailable;
|
| 115 | 120 | #else
|
| 116 | 121 | return IOManagerUnavailable;
|
| 122 | +#endif
|
|
| 123 | + }
|
|
| 124 | + else if (strcmp("uring", iomgrstr) == 0) {
|
|
| 125 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 126 | + *flag = IO_MNGR_FLAG_URING;
|
|
| 127 | + return IOManagerAvailable;
|
|
| 128 | +#else
|
|
| 129 | + return IOManagerUnavailable;
|
|
| 117 | 130 | #endif
|
| 118 | 131 | }
|
| 119 | 132 | else if (strcmp("mio", iomgrstr) == 0) {
|
| ... | ... | @@ -218,6 +231,8 @@ void selectIOManager(void) |
| 218 | 231 | iomgr_type = IO_MANAGER_SELECT;
|
| 219 | 232 | #elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
|
| 220 | 233 | iomgr_type = IO_MANAGER_POLL;
|
| 234 | +#elif defined(IOMGR_DEFAULT_NON_THREADED_URING)
|
|
| 235 | + iomgr_type = IO_MANAGER_URING;
|
|
| 221 | 236 | #elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
|
| 222 | 237 | iomgr_type = IO_MANAGER_WINIO;
|
| 223 | 238 | #elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
|
| ... | ... | @@ -240,6 +255,12 @@ void selectIOManager(void) |
| 240 | 255 | break;
|
| 241 | 256 | #endif
|
| 242 | 257 | |
| 258 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 259 | + case IO_MNGR_FLAG_URING:
|
|
| 260 | + iomgr_type = IO_MANAGER_URING;
|
|
| 261 | + break;
|
|
| 262 | +#endif
|
|
| 263 | + |
|
| 243 | 264 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 244 | 265 | case IO_MNGR_FLAG_MIO:
|
| 245 | 266 | iomgr_type = IO_MANAGER_MIO_POSIX;
|
| ... | ... | @@ -282,6 +303,10 @@ char * showIOManager(void) |
| 282 | 303 | case IO_MANAGER_POLL:
|
| 283 | 304 | return "poll";
|
| 284 | 305 | #endif
|
| 306 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 307 | + case IO_MANAGER_URING:
|
|
| 308 | + return "uring";
|
|
| 309 | +#endif
|
|
| 285 | 310 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 286 | 311 | case IO_MANAGER_MIO_POSIX:
|
| 287 | 312 | return "mio";
|
| ... | ... | @@ -335,6 +360,12 @@ void initCapabilityIOManager(Capability *cap) |
| 335 | 360 | break;
|
| 336 | 361 | #endif
|
| 337 | 362 | |
| 363 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 364 | + case IO_MANAGER_URING:
|
|
| 365 | + initCapabilityIOManagerURing(cap, iomgr);
|
|
| 366 | + break;
|
|
| 367 | +#endif
|
|
| 368 | + |
|
| 338 | 369 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 339 | 370 | case IO_MANAGER_WIN32_LEGACY:
|
| 340 | 371 | iomgr->blocked_queue_hd = END_TSO_QUEUE;
|
| ... | ... | @@ -378,6 +409,11 @@ void initIOManager(void) |
| 378 | 409 | break;
|
| 379 | 410 | #endif
|
| 380 | 411 | |
| 412 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 413 | + case IO_MANAGER_URING:
|
|
| 414 | + break;
|
|
| 415 | +#endif
|
|
| 416 | + |
|
| 381 | 417 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 382 | 418 | case IO_MANAGER_MIO_POSIX:
|
| 383 | 419 | /* Posix implementation in posix/Signals.c
|
| ... | ... | @@ -441,6 +477,18 @@ initIOManagerAfterFork(Capability **pcap) |
| 441 | 477 | */
|
| 442 | 478 | ioManagerStartCap(pcap);
|
| 443 | 479 | break;
|
| 480 | +#endif
|
|
| 481 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 482 | + //TODO: So currently there's no per-cap re-initialisation
|
|
| 483 | + // except for cap0.
|
|
| 484 | + case IO_MANAGER_URING:
|
|
| 485 | + barf("IOManager.c:initIOManagerAfterFork:URing:TODO");
|
|
| 486 | + {
|
|
| 487 | + for (unsigned int i = 0; i < getNumCapabilities(); i++) {
|
|
| 488 | + Capability *cap = getCapability(i);
|
|
| 489 | + initCapabilityIOManagerAfterForkURing(cap, cap->iomgr);
|
|
| 490 | + }
|
|
| 491 | + }
|
|
| 444 | 492 | #endif
|
| 445 | 493 | /* The IO_MANAGER_SELECT needs no initialisation */
|
| 446 | 494 | /* The IO_MANAGER_POLL needs no initialisation */
|
| ... | ... | @@ -452,7 +500,13 @@ initIOManagerAfterFork(Capability **pcap) |
| 452 | 500 | }
|
| 453 | 501 | |
| 454 | 502 | |
| 455 | -/* Called from setNumCapabilities.
|
|
| 503 | +/* Called from setNumCapabilities, after all other per-capability changes have
|
|
| 504 | + * been made. When the scheduler increases the number of capabilities, it
|
|
| 505 | + * (indirectly) calls initCapabilityIOManager, for each new capability. So this
|
|
| 506 | + * notification is only needed by I/O managers that need a global hook (not
|
|
| 507 | + * per-cap), and/or need to be notified of there being fewer (as well as more)
|
|
| 508 | + * capabilities. There is no per-capability notification for disabling a
|
|
| 509 | + * capability (which occurs when the number of capabilities is reduced).
|
|
| 456 | 510 | */
|
| 457 | 511 | void notifyIOManagerCapabilitiesChanged(Capability **pcap)
|
| 458 | 512 | {
|
| ... | ... | @@ -583,6 +637,18 @@ void markCapabilityIOManager(evac_fn evac, void *user, Capability *cap) |
| 583 | 637 | }
|
| 584 | 638 | #endif
|
| 585 | 639 | |
| 640 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 641 | + case IO_MANAGER_URING:
|
|
| 642 | + {
|
|
| 643 | + CapIOManager *iomgr = cap->iomgr;
|
|
| 644 | + markClosureTable(evac, user, &iomgr->aiop_table);
|
|
| 645 | + evac(user, (StgClosure **)(void *)&iomgr->overflow_tso_q_hd);
|
|
| 646 | + evac(user, (StgClosure **)(void *)&iomgr->overflow_tso_q_tl);
|
|
| 647 | + evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
|
|
| 648 | + break;
|
|
| 649 | + }
|
|
| 650 | +#endif
|
|
| 651 | + |
|
| 586 | 652 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 587 | 653 | case IO_MANAGER_WIN32_LEGACY:
|
| 588 | 654 | {
|
| ... | ... | @@ -608,8 +674,15 @@ void scavengeTSOIOManager(StgTSO *tso) |
| 608 | 674 | * both of these are not GC pointers, so there is nothing to do.
|
| 609 | 675 | */
|
| 610 | 676 | |
| 677 | +#if defined(IOMGR_ENABLED_POLL) \
|
|
| 678 | + || defined(IOMGR_ENABLED_URING)
|
|
| 679 | +
|
|
| 611 | 680 | #if defined(IOMGR_ENABLED_POLL)
|
| 612 | 681 | case IO_MANAGER_POLL:
|
| 682 | +#endif
|
|
| 683 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 684 | + case IO_MANAGER_URING:
|
|
| 685 | +#endif
|
|
| 613 | 686 | /* BlockedOn{Read,Write} uses block_info.aiop
|
| 614 | 687 | * BlockedOnDelay uses block_info.timeout
|
| 615 | 688 | * both of these are heap allocated, so we can do the same in all
|
| ... | ... | @@ -669,6 +742,12 @@ bool anyPendingTimeoutsOrIO(Capability *cap) |
| 669 | 742 | return anyPendingTimeoutsOrIOPoll(cap->iomgr);
|
| 670 | 743 | #endif
|
| 671 | 744 | |
| 745 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 746 | + case IO_MANAGER_URING:
|
|
| 747 | + return anyPendingTimeoutsOrIOURing(cap->iomgr);
|
|
| 748 | + //TODO: do we want to share code with poll here?
|
|
| 749 | +#endif
|
|
| 750 | + |
|
| 672 | 751 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 673 | 752 | case IO_MANAGER_WIN32_LEGACY:
|
| 674 | 753 | {
|
| ... | ... | @@ -732,6 +811,12 @@ void pollCompletedTimeoutsOrIO(Capability *cap) |
| 732 | 811 | break;
|
| 733 | 812 | #endif
|
| 734 | 813 | |
| 814 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 815 | + case IO_MANAGER_URING:
|
|
| 816 | + pollCompletedTimeoutsOrIOURing(cap);
|
|
| 817 | + break;
|
|
| 818 | +#endif
|
|
| 819 | + |
|
| 735 | 820 | #if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
|
| 736 | 821 | (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
|
| 737 | 822 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| ... | ... | @@ -765,6 +850,12 @@ void awaitCompletedTimeoutsOrIO(Capability *cap) |
| 765 | 850 | break;
|
| 766 | 851 | #endif
|
| 767 | 852 | |
| 853 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 854 | + case IO_MANAGER_URING:
|
|
| 855 | + awaitCompletedTimeoutsOrIOURing(cap);
|
|
| 856 | + break;
|
|
| 857 | +#endif
|
|
| 858 | + |
|
| 768 | 859 | #if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
|
| 769 | 860 | (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
|
| 770 | 861 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| ... | ... | @@ -805,8 +896,11 @@ int syncIOWaitReady(Capability *cap, |
| 805 | 896 | #endif
|
| 806 | 897 | #if defined(IOMGR_ENABLED_POLL)
|
| 807 | 898 | case IO_MANAGER_POLL:
|
| 808 | - ASSERT(tso->why_blocked == NotBlocked);
|
|
| 809 | 899 | return syncIOWaitReadyPoll(cap, tso, rw, fd);
|
| 900 | +#endif
|
|
| 901 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 902 | + case IO_MANAGER_URING:
|
|
| 903 | + return syncIOWaitReadyURing(cap, tso, rw, fd);
|
|
| 810 | 904 | #endif
|
| 811 | 905 | default:
|
| 812 | 906 | barf("waitRead# / waitWrite# not available for current I/O manager");
|
| ... | ... | @@ -814,6 +908,91 @@ int syncIOWaitReady(Capability *cap, |
| 814 | 908 | }
|
| 815 | 909 | |
| 816 | 910 | |
| 911 | +int syncIOReadWrite(Capability *cap, StgTSO *tso,
|
|
| 912 | + IOReadOrWrite rw, HsInt fd,
|
|
| 913 | + StgClosure *live, void *buf,
|
|
| 914 | + HsWord len)
|
|
| 915 | +{
|
|
| 916 | + debugTrace(DEBUG_iomanager,
|
|
| 917 | + "thread %ld %s fd %d", (long) tso->id,
|
|
| 918 | + rw == IORead ? "reading from" : "writing to", (int) fd);
|
|
| 919 | + ASSERT(tso->why_blocked == NotBlocked);
|
|
| 920 | + switch (iomgr_type) {
|
|
| 921 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 922 | + case IO_MANAGER_URING:
|
|
| 923 | + return syncIOReadWriteURing(cap, tso, rw, (int)fd, live, buf,
|
|
| 924 | + (size_t)len, (off_t)(-1));
|
|
| 925 | + /* off_t = -1 means use and update the file pointer. */
|
|
| 926 | +#endif
|
|
| 927 | +/*
|
|
| 928 | +#if defined(IOMGR_ENABLED_SELECT) \
|
|
| 929 | + || defined(IOMGR_ENABLED_POLL)
|
|
| 930 | +#if defined(IOMGR_ENABLED_SELECT)
|
|
| 931 | + case IO_MANAGER_SELECT:
|
|
| 932 | +#endif
|
|
| 933 | +#if defined(IOMGR_ENABLED_POLL)
|
|
| 934 | + case IO_MANAGER_POLL:
|
|
| 935 | +#endif
|
|
| 936 | + {
|
|
| 937 | + if (rw == IORead) {
|
|
| 938 | + read((int) fd, buf, (size_t) len);
|
|
| 939 | + } else {
|
|
| 940 | + write((int) fd, buf, (size_t) len);
|
|
| 941 | + }
|
|
| 942 | + //TODO: Ugg! We need to tell the primop to return synchonrously!
|
|
| 943 | + //Need to change the return type. Should use an out arg for the
|
|
| 944 | + //GC alloc retry.
|
|
| 945 | + }
|
|
| 946 | +#endif
|
|
| 947 | +*/
|
|
| 948 | + default:
|
|
| 949 | + barf("syncIORead/Write# not available for current I/O manager");
|
|
| 950 | + }
|
|
| 951 | +}
|
|
| 952 | + |
|
| 953 | +int syncIOReadWriteAt(Capability *cap, StgTSO *tso,
|
|
| 954 | + IOReadOrWrite rw, HsInt fd,
|
|
| 955 | + StgClosure *live, void *buf,
|
|
| 956 | + HsWord len, HsInt64 off)
|
|
| 957 | +{
|
|
| 958 | + debugTrace(DEBUG_iomanager,
|
|
| 959 | + "thread %ld %s fd %d", (long) tso->id,
|
|
| 960 | + rw == IORead ? "reading from" : "writing to", (int) fd);
|
|
| 961 | + ASSERT(tso->why_blocked == NotBlocked);
|
|
| 962 | + switch (iomgr_type) {
|
|
| 963 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 964 | + case IO_MANAGER_URING:
|
|
| 965 | + return syncIOReadWriteURing(cap, tso, rw,
|
|
| 966 | + (int)fd, live, buf,
|
|
| 967 | + (size_t)len, (off_t)off);
|
|
| 968 | +#endif
|
|
| 969 | +/*
|
|
| 970 | +#if defined(IOMGR_ENABLED_SELECT) \
|
|
| 971 | + || defined(IOMGR_ENABLED_POLL)
|
|
| 972 | +#if defined(IOMGR_ENABLED_SELECT)
|
|
| 973 | + case IO_MANAGER_SELECT:
|
|
| 974 | +#endif
|
|
| 975 | +#if defined(IOMGR_ENABLED_POLL)
|
|
| 976 | + case IO_MANAGER_POLL:
|
|
| 977 | +#endif
|
|
| 978 | + {
|
|
| 979 | + if (rw == IORead) {
|
|
| 980 | + pread((int)fd, buf, (size_t)len, (off_t)off);
|
|
| 981 | + } else {
|
|
| 982 | + pwrite((int)fd, buf, (size_t)len, (off_t)off);
|
|
| 983 | + }
|
|
| 984 | + //TODO: Ugg! We need to tell the primop to return synchonrously!
|
|
| 985 | + //Need to change the return type. Should use an out arg for the
|
|
| 986 | + //GC alloc retry.
|
|
| 987 | + }
|
|
| 988 | +#endif
|
|
| 989 | +*/
|
|
| 990 | + default:
|
|
| 991 | + barf("syncIORead/Write# not available for current I/O manager");
|
|
| 992 | + }
|
|
| 993 | +}
|
|
| 994 | + |
|
| 995 | + |
|
| 817 | 996 | void syncIOCancel(Capability *cap, StgTSO *tso)
|
| 818 | 997 | {
|
| 819 | 998 | debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long) tso->id);
|
| ... | ... | @@ -829,6 +1008,11 @@ void syncIOCancel(Capability *cap, StgTSO *tso) |
| 829 | 1008 | syncIOCancelPoll(cap, tso);
|
| 830 | 1009 | break;
|
| 831 | 1010 | #endif
|
| 1011 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 1012 | + case IO_MANAGER_URING:
|
|
| 1013 | + syncIOCancelURing(cap, tso);
|
|
| 1014 | + break;
|
|
| 1015 | +#endif
|
|
| 832 | 1016 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 833 | 1017 | case IO_MANAGER_WIN32_LEGACY:
|
| 834 | 1018 | removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
|
| ... | ... | @@ -862,8 +1046,15 @@ int syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 862 | 1046 | return 0;
|
| 863 | 1047 | }
|
| 864 | 1048 | #endif
|
| 1049 | +#if defined(IOMGR_ENABLED_POLL) \
|
|
| 1050 | + || defined(IOMGR_ENABLED_URING)
|
|
| 1051 | + |
|
| 865 | 1052 | #if defined(IOMGR_ENABLED_POLL)
|
| 866 | 1053 | case IO_MANAGER_POLL:
|
| 1054 | +#endif
|
|
| 1055 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 1056 | + case IO_MANAGER_URING:
|
|
| 1057 | +#endif
|
|
| 867 | 1058 | return syncDelayTimeout(cap, tso, us_delay);
|
| 868 | 1059 | #endif
|
| 869 | 1060 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| ... | ... | @@ -903,8 +1094,15 @@ void syncDelayCancel(Capability *cap, StgTSO *tso) |
| 903 | 1094 | removeThreadFromQueue(cap, &cap->iomgr->sleeping_queue, tso);
|
| 904 | 1095 | break;
|
| 905 | 1096 | #endif
|
| 1097 | +#if defined(IOMGR_ENABLED_POLL) \
|
|
| 1098 | + || defined(IOMGR_ENABLED_URING)
|
|
| 1099 | + |
|
| 906 | 1100 | #if defined(IOMGR_ENABLED_POLL)
|
| 907 | 1101 | case IO_MANAGER_POLL:
|
| 1102 | +#endif
|
|
| 1103 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 1104 | + case IO_MANAGER_URING:
|
|
| 1105 | +#endif
|
|
| 908 | 1106 | syncDelayCancelTimeout(cap, tso);
|
| 909 | 1107 | break;
|
| 910 | 1108 | #endif
|
| ... | ... | @@ -46,6 +46,9 @@ |
| 46 | 46 | #if defined(IOMGR_BUILD_POLL) && !defined(THREADED_RTS)
|
| 47 | 47 | #define IOMGR_ENABLED_POLL
|
| 48 | 48 | #endif
|
| 49 | +#if defined(IOMGR_BUILD_URING) && !defined(THREADED_RTS)
|
|
| 50 | + #define IOMGR_ENABLED_URING
|
|
| 51 | +#endif
|
|
| 49 | 52 | #if defined(IOMGR_BUILD_MIO) && defined(THREADED_RTS)
|
| 50 | 53 | /* For MIO, it is really two separate I/O manager implementations: one for
|
| 51 | 54 | * Windows and one for non-Windows. This is clear from both the C code on the
|
| ... | ... | @@ -110,6 +113,11 @@ |
| 110 | 113 | #else
|
| 111 | 114 | #define IOMGR_ENABLED_STR_POLL ""
|
| 112 | 115 | #endif
|
| 116 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 117 | + #define IOMGR_ENABLED_STR_URING " uring"
|
|
| 118 | +#else
|
|
| 119 | + #define IOMGR_ENABLED_STR_URING ""
|
|
| 120 | +#endif
|
|
| 113 | 121 | #if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
|
| 114 | 122 | #define IOMGR_ENABLED_STR_MIO " mio"
|
| 115 | 123 | #else
|
| ... | ... | @@ -128,6 +136,7 @@ |
| 128 | 136 | #define IOMGRS_ENABLED_STR \
|
| 129 | 137 | IOMGR_ENABLED_STR_SELECT \
|
| 130 | 138 | IOMGR_ENABLED_STR_POLL \
|
| 139 | + IOMGR_ENABLED_STR_URING \
|
|
| 131 | 140 | IOMGR_ENABLED_STR_MIO \
|
| 132 | 141 | IOMGR_ENABLED_STR_WINIO \
|
| 133 | 142 | IOMGR_ENABLED_STR_WIN32_LEGACY
|
| ... | ... | @@ -143,6 +152,9 @@ typedef enum { |
| 143 | 152 | #if defined(IOMGR_ENABLED_POLL)
|
| 144 | 153 | IO_MANAGER_POLL,
|
| 145 | 154 | #endif
|
| 155 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 156 | + IO_MANAGER_URING,
|
|
| 157 | +#endif
|
|
| 146 | 158 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 147 | 159 | IO_MANAGER_MIO_POSIX,
|
| 148 | 160 | #endif
|
| ... | ... | @@ -298,7 +310,7 @@ void scavengeTSOIOManager(StgTSO *tso); |
| 298 | 310 | /* Several code paths are almost identical between read and write paths. In
|
| 299 | 311 | * such cases we use a shared code path with an enum to say which we're doing.
|
| 300 | 312 | */
|
| 301 | -typedef enum { IORead, IOWrite } IOReadOrWrite;
|
|
| 313 | +typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite;
|
|
| 302 | 314 | |
| 303 | 315 | /* Synchronous operations: I/O and delays. As synchronous operations they
|
| 304 | 316 | * necessarily operate on threads. The thread is suspended until the operation
|
| ... | ... | @@ -310,7 +322,18 @@ typedef enum { IORead, IOWrite } IOReadOrWrite; |
| 310 | 322 | * GC to free up at least n words and then retry the operation.
|
| 311 | 323 | */
|
| 312 | 324 | |
| 313 | -int syncIOWaitReady(Capability *cap, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
|
|
| 325 | +int syncIOWaitReady(Capability *cap, StgTSO *tso,
|
|
| 326 | + IOReadOrWrite rw, HsInt fd);
|
|
| 327 | + |
|
| 328 | +int syncIOReadWrite(Capability *cap, StgTSO *tso,
|
|
| 329 | + IOReadOrWrite rw, HsInt fd,
|
|
| 330 | + StgClosure *live, void *buf,
|
|
| 331 | + HsWord len);
|
|
| 332 | + |
|
| 333 | +int syncIOReadWriteAt(Capability *cap, StgTSO *tso,
|
|
| 334 | + IOReadOrWrite rw, HsInt fd,
|
|
| 335 | + StgClosure *live, void *buf,
|
|
| 336 | + HsWord len, HsInt64 off);
|
|
| 314 | 337 | |
| 315 | 338 | void syncIOCancel(Capability *cap, StgTSO *tso);
|
| 316 | 339 |
| ... | ... | @@ -42,7 +42,8 @@ struct _CapIOManager { |
| 42 | 42 | StgTSO *sleeping_queue;
|
| 43 | 43 | #endif
|
| 44 | 44 | |
| 45 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 45 | +#if defined(IOMGR_ENABLED_POLL) \
|
|
| 46 | + || defined(IOMGR_ENABLED_URING)
|
|
| 46 | 47 | /* AIOP and timeout collections shared by several I/O manager impls */
|
| 47 | 48 | ClosureTable aiop_table;
|
| 48 | 49 | StgTimeoutQueue *timeout_queue;
|
| ... | ... | @@ -53,6 +54,54 @@ struct _CapIOManager { |
| 53 | 54 | struct pollfd *aiop_poll_table;
|
| 54 | 55 | #endif
|
| 55 | 56 | |
| 57 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 58 | + /* io_uring library structure */
|
|
| 59 | + struct io_uring *uring;
|
|
| 60 | + |
|
| 61 | + /* The number of operations submitted (by Haskell threads to the I/O
|
|
| 62 | + manager) and not yet notified of completion. */
|
|
| 63 | + int n_submitted_b; /* for blocking operations */
|
|
| 64 | + int n_submitted_nb; /* for non-blocking operations */
|
|
| 65 | + |
|
| 66 | + /* The number of operations pending in the submission queue, but not yet
|
|
| 67 | + submitted to the kernel (so not in-flight). */
|
|
| 68 | + int n_prepared_b; /* for blocking operations */
|
|
| 69 | + int n_prepared_nb; /* for non-blocking operations */
|
|
| 70 | + |
|
| 71 | + /* The number of operations submitted to the kernel but where the
|
|
| 72 | + corresponding completion has not yet been processed. */
|
|
| 73 | + int n_inflight_b; /* for blocking operations */
|
|
| 74 | + int n_inflight_nb; /* for non-blocking operations */
|
|
| 75 | + |
|
| 76 | + /* The limit on the number of operations we allow to be in-flight */
|
|
| 77 | + int limit_inflight_b; /* for blocking operations */
|
|
| 78 | + int limit_inflight_nb; /* for non-blocking operations */
|
|
| 79 | + |
|
| 80 | + /* The number of operations pending in the overflow queue (so not in the
|
|
| 81 | + submission queue or in flight) */
|
|
| 82 | + /* no overflow for blocking operations */
|
|
| 83 | + int n_overflow_nb; /* for non-blocking operations */
|
|
| 84 | +
|
|
| 85 | + /* Invariants:
|
|
| 86 | + n_submitted_b = n_prepared_b + n_inflight_b
|
|
| 87 | + n_submitted_nb = n_prepared_nb + n_inflight_nb + n_overflow_nb
|
|
| 88 | + n_prepared_b + n_prepared_nb <= size of submission queue
|
|
| 89 | + */
|
|
| 90 | + |
|
| 91 | + /* A queue of threads blocked on I/O submission and a parallel queue of
|
|
| 92 | + * their corresponding SQEs. This is only used when there are more pending
|
|
| 93 | + * (non-blocking) I/O operations than the inflight limit.
|
|
| 94 | + */
|
|
| 95 | + StgTSO *overflow_tso_q_hd, *overflow_tso_q_tl;
|
|
| 96 | + struct overflow_sqe_q_t {
|
|
| 97 | + struct io_uring_sqe *sqe;
|
|
| 98 | + struct overflow_sqe_q_t *next;
|
|
| 99 | +#if defined(DEBUG)
|
|
| 100 | + StgThreadID tid;
|
|
| 101 | +#endif
|
|
| 102 | + } *overflow_sqe_q_hd, *overflow_sqe_q_tl;
|
|
| 103 | +#endif
|
|
| 104 | + |
|
| 56 | 105 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| 57 | 106 | /* Thread queue for threads blocked on I/O completion. */
|
| 58 | 107 | StgTSO *blocked_queue_hd;
|
| ... | ... | @@ -2602,6 +2602,108 @@ stg_delayzh ( W_ us_delay ) |
| 2602 | 2602 | }
|
| 2603 | 2603 | }
|
| 2604 | 2604 | |
| 2605 | +/*
|
|
| 2606 | +int syncIOReadWrite(Capability *cap, StgTSO *tso,
|
|
| 2607 | + IOReadOrWrite rw, HsInt fd,
|
|
| 2608 | + StgClosure *live, void *buf,
|
|
| 2609 | + HsWord len);
|
|
| 2610 | + |
|
| 2611 | +int syncIOReadWriteAt(Capability *cap, StgTSO *tso,
|
|
| 2612 | + IOReadOrWrite rw, HsInt fd,
|
|
| 2613 | + StgClosure *live, void *buf,
|
|
| 2614 | + HsWord len, HsInt64 off);
|
|
| 2615 | +*/
|
|
| 2616 | + |
|
| 2617 | +#define SYNCIO_BLOCK_OR_GC(fail) \
|
|
| 2618 | + if (fail == 0) (likely: True) { \
|
|
| 2619 | + jump stg_block_noregs(); \
|
|
| 2620 | + } else { \
|
|
| 2621 | + /* TODO: should invoke GC, requesting 'fail' words, then retry \
|
|
| 2622 | + see: https://gitlab.haskell.org/ghc/ghc/-/issues/24105 */ \
|
|
| 2623 | + jump stg_raisezh(ghczminternal_GHCziInternalziIOziException_heapOverflow_closure); \
|
|
| 2624 | + }
|
|
| 2625 | + //TODO: need to handle returning synchonrously, for non-uring I/O managers.
|
|
| 2626 | + //or if uring optimises the read case.
|
|
| 2627 | + |
|
| 2628 | +stg_syncIOReadAddrzh ( W_ fd, W_ buf, W_ len )
|
|
| 2629 | +{
|
|
| 2630 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2631 | + (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2632 | + 0::I32 /* IORead */, fd,
|
|
| 2633 | + stg_ASYNCIO_LIVE0_closure, buf,
|
|
| 2634 | + len);
|
|
| 2635 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2636 | +}
|
|
| 2637 | + |
|
| 2638 | +stg_syncIOReadByteArrayzh ( W_ fd, P_ buf, W_ boff, W_ len )
|
|
| 2639 | +{
|
|
| 2640 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2641 | + (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2642 | + 0::I32 /* IORead */, fd,
|
|
| 2643 | + buf, buf + SIZEOF_StgArrBytes + boff,
|
|
| 2644 | + len);
|
|
| 2645 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2646 | +}
|
|
| 2647 | + |
|
| 2648 | +stg_syncIOReadAddrAtzh ( W_ fd, W_ buf, W_ len, I64 foff )
|
|
| 2649 | +{
|
|
| 2650 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2651 | + (fail) = ccall syncIOReadWriteAt(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2652 | + 0::I32 /* IORead */, fd,
|
|
| 2653 | + stg_ASYNCIO_LIVE0_closure, buf,
|
|
| 2654 | + len, foff);
|
|
| 2655 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2656 | +}
|
|
| 2657 | + |
|
| 2658 | +stg_syncIOReadByteArrayAtzh ( W_ fd, P_ buf, W_ boff, W_ len, I64 foff )
|
|
| 2659 | +{
|
|
| 2660 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2661 | + (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2662 | + 0::I32 /* IORead */, fd,
|
|
| 2663 | + buf, buf + SIZEOF_StgArrBytes + boff,
|
|
| 2664 | + len, foff);
|
|
| 2665 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2666 | +}
|
|
| 2667 | + |
|
| 2668 | +stg_syncIOWriteAddrzh ( W_ fd, W_ buf, W_ len )
|
|
| 2669 | +{
|
|
| 2670 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2671 | + (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2672 | + 1::I32 /* IOWrite */, fd,
|
|
| 2673 | + stg_ASYNCIO_LIVE0_closure, buf,
|
|
| 2674 | + len);
|
|
| 2675 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2676 | +}
|
|
| 2677 | + |
|
| 2678 | +stg_syncIOWriteByteArrayzh ( W_ fd, P_ buf, W_ boff, W_ len )
|
|
| 2679 | +{
|
|
| 2680 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2681 | + (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2682 | + 1::I32 /* IOWrite */, fd,
|
|
| 2683 | + buf, buf + SIZEOF_StgArrBytes + boff,
|
|
| 2684 | + len);
|
|
| 2685 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2686 | +}
|
|
| 2687 | + |
|
| 2688 | +stg_syncIOWriteAddrAtzh ( W_ fd, W_ buf, W_ len, I64 foff )
|
|
| 2689 | +{
|
|
| 2690 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2691 | + (fail) = ccall syncIOReadWriteAt(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2692 | + 1::I32 /* IOWrite */, fd,
|
|
| 2693 | + stg_ASYNCIO_LIVE0_closure, buf,
|
|
| 2694 | + len, foff);
|
|
| 2695 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2696 | +}
|
|
| 2697 | + |
|
| 2698 | +stg_syncIOWriteByteArrayAtzh ( W_ fd, P_ buf, W_ boff, W_ len, I64 foff )
|
|
| 2699 | +{
|
|
| 2700 | + W_ fail; /* Request this many words on heap alloc failure. */
|
|
| 2701 | + (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2702 | + 1::I32 /* IOWrite */, fd,
|
|
| 2703 | + buf, buf + SIZEOF_StgArrBytes + boff,
|
|
| 2704 | + len, foff);
|
|
| 2705 | + SYNCIO_BLOCK_OR_GC(fail);
|
|
| 2706 | +}
|
|
| 2605 | 2707 | |
| 2606 | 2708 | #if defined(mingw32_HOST_OS)
|
| 2607 | 2709 | stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf )
|
| ... | ... | @@ -397,6 +397,23 @@ GHC_IOMANAGER_ENABLE([poll], [EnableIOManagerPoll], [IOMGR_BUILD_POLL], |
| 397 | 397 | #include <poll.h>])
|
| 398 | 398 | fi])
|
| 399 | 399 | |
| 400 | +GHC_IOMANAGER_ENABLE([uring], [EnableIOManagerURing], [IOMGR_BUILD_URING],
|
|
| 401 | + [if test "$HostOS" = "linux"; then
|
|
| 402 | + AC_CHECK_HEADER([liburing.h], [HaveLiburingH=YES],
|
|
| 403 | + [AC_MSG_WARN([liburing.h is required by the uring I/O manager])],[])
|
|
| 404 | + AC_CHECK_LIB([uring], [io_uring_check_version], [HaveLiburing=YES],
|
|
| 405 | + [AC_MSG_WARN([liburing is required by the uring I/O manager])], [])
|
|
| 406 | + if test "$HaveLiburingH" = "YES" && test "$HaveLiburing" = YES; then
|
|
| 407 | + EnableIOManagerURing=YES
|
|
| 408 | + LinkLiburing=uring
|
|
| 409 | + else
|
|
| 410 | + EnableIOManagerURing=NO
|
|
| 411 | + fi
|
|
| 412 | + else
|
|
| 413 | + EnableIOManagerURing=NO
|
|
| 414 | + fi])
|
|
| 415 | +AC_SUBST(LinkLiburing)
|
|
| 416 | + |
|
| 400 | 417 | GHC_IOMANAGER_ENABLE([mio], [EnableIOManagerMIO], [IOMGR_BUILD_MIO],
|
| 401 | 418 | [EnableIOManagerMIO=YES])
|
| 402 | 419 | |
| ... | ... | @@ -415,6 +432,7 @@ if test "$HostOS" = "mingw32"; then |
| 415 | 432 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [winio], [EnableIOManagerWinIO])
|
| 416 | 433 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
|
| 417 | 434 | else
|
| 435 | + GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [uring], [EnableIOManagerURing])
|
|
| 418 | 436 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [select], [EnableIOManagerSelect])
|
| 419 | 437 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [poll], [EnableIOManagerPoll])
|
| 420 | 438 | GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
|
| ... | ... | @@ -432,6 +450,9 @@ GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded], |
| 432 | 450 | GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
|
| 433 | 451 | [poll], [IOMGR_DEFAULT_NON_THREADED_POLL])
|
| 434 | 452 | |
| 453 | +GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
|
|
| 454 | + [uring], [IOMGR_DEFAULT_NON_THREADED_URING])
|
|
| 455 | + |
|
| 435 | 456 | GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
|
| 436 | 457 | [winio], [IOMGR_DEFAULT_NON_THREADED_WINIO])
|
| 437 | 458 |
| ... | ... | @@ -271,7 +271,9 @@ |
| 271 | 271 | by tryWakeupThread() */
|
| 272 | 272 | #define ThreadMigrating 13
|
| 273 | 273 | |
| 274 | -/* Next number is 15. */
|
|
| 274 | +#define BlockedOnIOSubmission 15
|
|
| 275 | + |
|
| 276 | +/* Next number is 16. */
|
|
| 275 | 277 | |
| 276 | 278 | /*
|
| 277 | 279 | * These constants are returned to the scheduler by a thread that has
|
| ... | ... | @@ -247,6 +247,7 @@ typedef enum _IO_MANAGER_FLAG { |
| 247 | 247 | /* All other choices pick only the requested one, with no fallback. */
|
| 248 | 248 | IO_MNGR_FLAG_SELECT, /* Unix only, non-threaded RTS only */
|
| 249 | 249 | IO_MNGR_FLAG_POLL, /* Unix only, non-threaded RTS only */
|
| 250 | + IO_MNGR_FLAG_URING, /* Linux only, non-threaded RTS only */
|
|
| 250 | 251 | IO_MNGR_FLAG_MIO, /* cross-platform, threaded RTS only */
|
| 251 | 252 | IO_MNGR_FLAG_WINIO, /* Windows only */
|
| 252 | 253 | IO_MNGR_FLAG_WIN32_LEGACY, /* Windows only, non-threaded RTS only */
|
| ... | ... | @@ -272,6 +273,8 @@ typedef struct _MISC_FLAGS { |
| 272 | 273 | * for the linker, NULL ==> off */
|
| 273 | 274 | IO_MANAGER_FLAG ioManager; /* The I/O manager to use. */
|
| 274 | 275 | uint32_t numIoWorkerThreads; /* Number of I/O worker threads to use. */
|
| 276 | + uint32_t io_uring_sq_entries; /* io_uring submission queue size */
|
|
| 277 | + uint32_t io_uring_cq_entries; /* io_uring completion queue size */
|
|
| 275 | 278 | } MISC_FLAGS;
|
| 276 | 279 | |
| 277 | 280 | /* See Note [Synchronization of flags and base APIs] */
|
| ... | ... | @@ -713,7 +713,13 @@ union NotifyCompletion { |
| 713 | 713 | enum NotifyCompletionType {
|
| 714 | 714 | NotifyTSO = 0,
|
| 715 | 715 | NotifyMVar = 1,
|
| 716 | - NotifyTVar = 2
|
|
| 716 | + NotifyTVar = 2,
|
|
| 717 | + NotifyNone = 3
|
|
| 718 | + /* If a TSO receives an async exception while it's waiting on I/O then
|
|
| 719 | + * the TSO stops waiting but the I/O may still be outstanding, and we
|
|
| 720 | + * need the corresponding StgAsyncIOOp until the I/O completes. We use
|
|
| 721 | + * NotifyNone in this case to avoid disturbing the original TSO.
|
|
| 722 | + */
|
|
| 717 | 723 | };
|
| 718 | 724 | |
| 719 | 725 | /* A node in the leftist heap. */
|
| ... | ... | @@ -786,9 +792,9 @@ typedef struct { |
| 786 | 792 | // to know which capability an aiop is on.
|
| 787 | 793 | uint16_t capno;
|
| 788 | 794 | |
| 789 | - // Tells us which thing the notify union above contains.
|
|
| 790 | - // This is a value from enum IONotify but we don't use the enum type
|
|
| 791 | - // here due to portability concerns for this size C enum bitfield.
|
|
| 795 | + // Tells us which thing the notify union above contains. This is a value
|
|
| 796 | + // from enum NotifyCompletionType but we don't use the enum type here
|
|
| 797 | + // due to portability concerns for this size C enum bitfield.
|
|
| 792 | 798 | uint16_t notify_type: 2;
|
| 793 | 799 | |
| 794 | 800 | // The outcome:
|
| ... | ... | @@ -816,3 +822,15 @@ typedef struct { |
| 816 | 822 | // We handle this in the INFO_TABLE_CONSTR decl for stg_ASYNCIOOP using
|
| 817 | 823 | // Either32Or64Bit(4,2) for the non-pointer words.
|
| 818 | 824 | } StgAsyncIOOp;
|
| 825 | + |
|
| 826 | +struct io_uring_sqe;
|
|
| 827 | +typedef struct {
|
|
| 828 | + StgHeader header;
|
|
| 829 | + |
|
| 830 | + // Any heap object to keep alive for the duration of the I/O operation,
|
|
| 831 | + // for example I/O buffers.
|
|
| 832 | + StgClosure *live;
|
|
| 833 | +
|
|
| 834 | + struct io_uring_sqe *sqe;
|
|
| 835 | +} StgAsyncURingSQE;
|
|
| 836 | + |
| ... | ... | @@ -347,7 +347,7 @@ void pollCompletedTimeoutsOrIOPoll(Capability *cap) |
| 347 | 347 | #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
| 348 | 348 | /* We could use poll here, since we use no timeout, but for
|
| 349 | 349 | consistency we use the same syscall as at the other call site. */
|
| 350 | - struct timespec tv = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 };
|
|
| 350 | + struct timespec tv = { .tv_sec = 0, .tv_nsec = 0 };
|
|
| 351 | 351 | int res = ppoll(iomgr->aiop_poll_table, nfds, &tv, NULL);
|
| 352 | 352 | |
| 353 | 353 | debugTrace(DEBUG_iomanager,
|
| 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 | + |
|
| 12 | +#include "IOManager.h" // defines IOMGR_ENABLED_URING
|
|
| 13 | + |
|
| 14 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 15 | + |
|
| 16 | +#include "Capability.h"
|
|
| 17 | +#include "Threads.h"
|
|
| 18 | +#include "Schedule.h"
|
|
| 19 | +#include "Prelude.h"
|
|
| 20 | +#include "RtsUtils.h"
|
|
| 21 | +#include "rts/Time.h"
|
|
| 22 | +#include "RaiseAsync.h"
|
|
| 23 | + |
|
| 24 | +#include "URing.h"
|
|
| 25 | +#include "Signals.h"
|
|
| 26 | + |
|
| 27 | +#include <liburing.h>
|
|
| 28 | +#include <poll.h> // for poll() flags POLLIN POLLOUT
|
|
| 29 | +#include <limits.h>
|
|
| 30 | +#include <errno.h>
|
|
| 31 | +#include <fcntl.h>
|
|
| 32 | + |
|
| 33 | +#include "IOManagerInternals.h"
|
|
| 34 | +#include "Timeout.h"
|
|
| 35 | + |
|
| 36 | +/******************************************************************************
|
|
| 37 | + |
|
| 38 | +This I/O manager is based on the Linux io_uring API. We rely on the liburing
|
|
| 39 | +library, rather than using the system calls directly.
|
|
| 40 | + |
|
| 41 | +Introduction
|
|
| 42 | +============
|
|
| 43 | + |
|
| 44 | +The io_uring API is an _almost_ generic mechanism for performing Linux syscalls
|
|
| 45 | +asynchronously. It supports a range of I/O related operations, including
|
|
| 46 | +ordinary file read and write, and waiting for I/O readiness. It works using a
|
|
| 47 | +queue to submit I/O operations, and another queue to receive I/O completions.
|
|
| 48 | +The io_uring documentation calls these the "submission queue", abbreviated SQ,
|
|
| 49 | +and "completion queue", abbreviated CQ. The corresponding queue entries are
|
|
| 50 | +abbreviated as SQEs and CQEs.
|
|
| 51 | + |
|
| 52 | +There is a single system call to both submit operations and/or wait for I/O
|
|
| 53 | +completion (or a timeout). It is also possible to poll for new entries in the
|
|
| 54 | +completion queue without using a system call at all. This fits the RTS
|
|
| 55 | +scheduler design quite well. Every time round the scheduler loop we have to
|
|
| 56 | +do a non-blocking check for I/O completion, and it is only when there are no
|
|
| 57 | +runnable threads that we want to block and wait for I/O (or timers). So in busy
|
|
| 58 | +applications there are a lot more non-blocking than blocking checks for I/O
|
|
| 59 | +completion. So being able to do the non-blocking poll without needing a system
|
|
| 60 | +call should save significantly on system calls, compared to other APIs.
|
|
| 61 | + |
|
| 62 | +We use the liburing C library, rather than the system calls directly. This
|
|
| 63 | +provides a degree of convenience and portability across kernel versions.
|
|
| 64 | + |
|
| 65 | +Synchronous I/O
|
|
| 66 | +---------------
|
|
| 67 | + |
|
| 68 | +Classically, asynchronous I/O APIs are slower than ordinary synchronous I/O
|
|
| 69 | +APIs for the case of buffered I/O reads where the requested data is already in
|
|
| 70 | +the OS page cache (if they support buffered I/O at all). This requires
|
|
| 71 | +applications that use asynchronous I/O (and thus care about performance) to use
|
|
| 72 | +it only in some cases, and use synchronous I/O when that is expected to be
|
|
| 73 | +faster. This is complex, and thus relatively few applications use asynchronous
|
|
| 74 | +I/O. Furthermore, knowing whether data is in the page cache is something that
|
|
| 75 | +only the kernel knows reliably. User space can only make educated guesses.
|
|
| 76 | + |
|
| 77 | +A major selling point of io_uring is that it (mostly) solves this problem. It
|
|
| 78 | +is designed work for buffered I/O (as well as direct I/O). Operations are
|
|
| 79 | +submitted by putting entries (SQEs) into the submission queue (SQ) and then
|
|
| 80 | +entering the kernel (io_uring_enter). The kernel starts work on all the
|
|
| 81 | +operations. If any of them can complete synchronously then the kernel can place
|
|
| 82 | +the completion entries (CQEs) into the completion queue (CQ). Thus the
|
|
| 83 | +synchronous operations complete synchronously. Furthermore, the decision about
|
|
| 84 | +whether it can complete synchronously is made dynamically by the kernel (e.g.
|
|
| 85 | +based on whether the data is available in the page cache). The result is that a
|
|
| 86 | +simple file read where the data is available in the page cache can be as quick
|
|
| 87 | +as a normal synchronous file read system call.
|
|
| 88 | + |
|
| 89 | +Overall, this allows the same API to be used for all I/O, without having to
|
|
| 90 | +guess about synchronous vs asynchronous completions. Furthermore, io_uring
|
|
| 91 | +supports both disk I/O and sockets I/O, whereas older APIs only supported one
|
|
| 92 | +or the other (see e.g. epoll for sockets and Linux/Posix AIO for disk files).
|
|
| 93 | +Overall this allows a less complex solution, by using the same relatively
|
|
| 94 | +uniform API for everything (even through the API itself is somewhat more
|
|
| 95 | +complex than other APIs).
|
|
| 96 | + |
|
| 97 | +General strategy
|
|
| 98 | +----------------
|
|
| 99 | + |
|
| 100 | +Our I/O strategy with io_uring is to prepare but not submit I/O operations in
|
|
| 101 | +the I/O primops, and then submit the operations in the scheduler. That is, in
|
|
| 102 | +the I/O primops we put the I/O operations (SQEs) into the submission queue (SQ),
|
|
| 103 | +but we don't yet call into the kernel to inform it of the new operations.
|
|
| 104 | +Instead we do that in the scheduler. At this point in the scheduler, we can
|
|
| 105 | +both submit any pending I/O operations and handle any completions. This lets us
|
|
| 106 | +submit and collect I/O with a single system call. Handling I/O completions
|
|
| 107 | +typically results in waking up threads, which the scheduler can then deal with.
|
|
| 108 | + |
|
| 109 | +Consider the important example of simple disk reads that complete synchronously.
|
|
| 110 | +In this case the completion is available immediately after io_uring_enter
|
|
| 111 | +returns and the scheduler can reschedule the thread that submitted the I/O. So
|
|
| 112 | +the end result _should_ be as fast as a normal synchronous blocking read()
|
|
| 113 | +system call (but benchmarks are needed to verify this).
|
|
| 114 | + |
|
| 115 | +Important fast-path cases
|
|
| 116 | +-------------------------
|
|
| 117 | + |
|
| 118 | +There are a few important special fast-path cases to keep in mind:
|
|
| 119 | + |
|
| 120 | +1. Common case: as above, a Haskell thread has pending I/O, so we submit
|
|
| 121 | + it. This requires a system call. Afterwards we also process any
|
|
| 122 | + completions. This means any I/O operations that complete synchronously
|
|
| 123 | + get handled immediately and with a single system call. This is crucial
|
|
| 124 | + for performance of ordinary buffered I/O in the common case that the data
|
|
| 125 | + is available in the page cache.
|
|
| 126 | + |
|
| 127 | +2. Common case: no pending I/O to submit, but there is outstanding I/O
|
|
| 128 | + that we need to see if it has completed. This does not require a
|
|
| 129 | + system call. We just need to look at the completion ring.
|
|
| 130 | + |
|
| 131 | +3. Rare case: we have pending I/O to submit but the kernel refuses to
|
|
| 132 | + accept more I/O because the completion queue is full. In this case
|
|
| 133 | + we process the completion queue first, and then retry. This ends up
|
|
| 134 | + using two system calls.
|
|
| 135 | + |
|
| 136 | +I/O submission overload
|
|
| 137 | +-----------------------
|
|
| 138 | + |
|
| 139 | +Asynchronous I/O APIs enable I/O to be submitted without waiting and thus they
|
|
| 140 | +must deal with the problem of having too much I/O in flight at once for the
|
|
| 141 | +resources available.
|
|
| 142 | + |
|
| 143 | +For example, epoll_ctl will return ENOMEM or ENOSPC if it cannot allocate the
|
|
| 144 | +necessary memory or hits a resource limit. The epoll backend for MIO turns
|
|
| 145 | +these failures into Haskell exceptions. This works ok in practice for epoll
|
|
| 146 | +because epoll only supports one kind of async I/O operation: waiting for I/O
|
|
| 147 | +readiness. This has relatively low resource use and so systems generally hit
|
|
| 148 | +other resource limits first, e.g. file descriptors.
|
|
| 149 | + |
|
| 150 | +On the other hand io_uring supports a variety of I/O operations with different
|
|
| 151 | +behaviour and resource use. They can roughly be divided into two categories:
|
|
| 152 | +1. cheap blocking operations; and
|
|
| 153 | +2. expensive non-blocking operations.
|
|
| 154 | + |
|
| 155 | +The cheap blocking operations include waiting for I/O readiness on pipes and
|
|
| 156 | +sockets, as with epoll, but it also includes blocking reads/write/send/recv on
|
|
| 157 | +pipes and sockets. These operations tend to have very low resource use and
|
|
| 158 | +correspondingly the kernel can support very many outstanding operations. This
|
|
| 159 | +is needed for some large scale networking use cases where there can be very
|
|
| 160 | +many sockets in use at once.
|
|
| 161 | + |
|
| 162 | +The comparatively expensive non-blocking operations include read and write on
|
|
| 163 | +disk files and various file and file system operations. These operations tend
|
|
| 164 | +to have higher resource use and so the kernel cannot support too many of them
|
|
| 165 | +in progress at once. Furthermore, for non-blocking operations there isn't much
|
|
| 166 | +need to have huge numbers in progress at once: the performance benefit is
|
|
| 167 | +limited by the hardware concurrency (CPUs, SSD queue depth etc).
|
|
| 168 | + |
|
| 169 | +Unfortunately, the limits on the number of concurrent operations of each kind
|
|
| 170 | +is not known, or at least not reported by the kernel in advance. When trying
|
|
| 171 | +to submit new operations, the kernel can report EAGAIN if it is out of
|
|
| 172 | +resources. This reporting mechanism is awkward because of the two classes of
|
|
| 173 | +operations. The way we would like to respond to hitting a limit is also
|
|
| 174 | +different for blocking vs non-blocking operations.
|
|
| 175 | + |
|
| 176 | +For non-blocking operations, we can simply wait for some operations to complete
|
|
| 177 | +and then submit more. This works because non-blocking operations will
|
|
| 178 | +eventually complete, and indeed typically complete fairly promptly.
|
|
| 179 | + |
|
| 180 | +For blocking operations on the other hand, there's not a lot we can do if we
|
|
| 181 | +hit a resource limit. Waiting may not help. Blocking operations can block
|
|
| 182 | +indefinitely. It is also possible to deadlock if not all I/O readiness
|
|
| 183 | +notifications are active simultaneously. So if we hit a resource limit for
|
|
| 184 | +these operations then we can't do much better than throw exceptions to the
|
|
| 185 | +Haskell threads submitting the operations. This is of course what the MIO epoll
|
|
| 186 | +backend does.
|
|
| 187 | + |
|
| 188 | +We will make the assumption that the kernel does _not_ have separate limits for
|
|
| 189 | +these two classes of operations, but assume that it is a common limit based on
|
|
| 190 | +the common resource of kernel memory. As noted above, there is no great benefit
|
|
| 191 | +to having excessive concurrency of non-blocking operations. We can limit the
|
|
| 192 | +concurrency (using a queue) with no change in semantics of the application. On
|
|
| 193 | +the other hand an application that wants to wait on zillions of blocking
|
|
| 194 | +operations cannot have that concurrency reduced without it having a semantic
|
|
| 195 | +effect on the application. We either have to support it or (hopefully
|
|
| 196 | +gracefully) fail as resources run out.
|
|
| 197 | + |
|
| 198 | +So we take the approach of trying to limit the concurrency and thus resource
|
|
| 199 | +use of the non-blocking operations so that the remaining kernel memory can
|
|
| 200 | +maximise the number of blocking operations we can support. We do that by
|
|
| 201 | +imposing a "reasonable" limit on the number of concurrent non-blocking
|
|
| 202 | +operations. We also impose a separate larger limit on the number of concurrent
|
|
| 203 | +blocking operations. The high level idea is that we treat the non-blocking
|
|
| 204 | +limit like a semaphore (with that concurrency limit) on the threads submitting
|
|
| 205 | +non-blocking I/O operations. This means we can handle overload transparently.
|
|
| 206 | +On the other hand the limit on blocking operations is a hard limit and if we
|
|
| 207 | +hit that then we have to throw exceptions (to the threads submitting the I/O).
|
|
| 208 | + |
|
| 209 | +In the typical application use cases this strategy should avoid encountering
|
|
| 210 | +EAGAIN in the first place, however it can still happen and we must handle it
|
|
| 211 | +somehow.
|
|
| 212 | + |
|
| 213 | +Before considering EAGAIN, consider a more normal scenario with lots of
|
|
| 214 | +concurrent I/O -- both blocking and non-blocking operations -- where we do hit
|
|
| 215 | +the limit on non-blocking operations. We track the number of blocking and
|
|
| 216 | +non-blocking operations that are currently "in flight" using the counters
|
|
| 217 | +iomgr->uring_inflight_{non}blocking_aiops. These are incremented by the number
|
|
| 218 | +of prepared operations when the submission queue is successfully flushed, and
|
|
| 219 | +decremented for each completion processed. Hitting the limit means that when a
|
|
| 220 | +thread submits a new non-blocking operation we find the count of inflight
|
|
| 221 | +non-blocking operations is already at the limit, and thus a new operation would
|
|
| 222 | +be over the limit.
|
|
| 223 | + |
|
| 224 | +Normally when a thread uses a primop to submit an I/O operation we would grab
|
|
| 225 | +an existing SQE from the submission queue (SQ) and fill in the SQE. If however
|
|
| 226 | +we would go over the inflight limit, then instead we allocate a fresh SQE (on
|
|
| 227 | +the C heap using malloc) and then fill in the SQE as normal. Then we put the
|
|
| 228 | +SQE and the TSO for the thread that submitted it onto (the end of) a pair of
|
|
| 229 | +overflow queues: one for the SQEs and one for the corresponding threads. We use
|
|
| 230 | +a pair of queues rather than a queue of pairs because the TSOs are allocated on
|
|
| 231 | +the GC heap but the SQEs are on the C heap and there is existing infrastructure
|
|
| 232 | +for handling TSO queues, including tracing them for GC. In the scheduler, when
|
|
| 233 | +we process the completion of a blocking operation, if there are entries on the
|
|
| 234 | +overflow queues then we can dequeue an SQE and corresponding TSO and copy the
|
|
| 235 | +SQE into the SQ and reschedule the TSO. This means the SQE is ready to go and
|
|
| 236 | +the TSO is unblocked. If we are in this overflow situation and the completions
|
|
| 237 | +for several non-blocking operations are processed in one go then will will add
|
|
| 238 | +several SQEs to be added to the SQ in one batch. This should naturally lead to
|
|
| 239 | +batching in the overflow situation and amortise the syscall overheads.
|
|
| 240 | + |
|
| 241 | +While all of this is going on, other threads submitting _blocking_ operations
|
|
| 242 | +can proceed as normal, preparing their operations into the submission queue.
|
|
| 243 | + |
|
| 244 | +Now if we do encounter EAGAIN, we assume that it's the blocking operations
|
|
| 245 | +that are at fault.
|
|
| 246 | + |
|
| 247 | +Approaches to batching
|
|
| 248 | +----------------------
|
|
| 249 | + |
|
| 250 | +We currently do no batching, but this section discusses plausible approaches.
|
|
| 251 | + |
|
| 252 | +With this design we have the opportunity to try to accumulate multiple pending
|
|
| 253 | +I/O operations and then submit them in one batch, which could improve
|
|
| 254 | +performance in I/O intensive applications by reducing the number of system
|
|
| 255 | +calls. Ordinary synchronous I/O primops like threadWaitRead# block the Haskell
|
|
| 256 | +thread and return to the scheduler, which means we only accumulate a single
|
|
| 257 | +pending I/O operation. There are a couple ways we could achieve batching.
|
|
| 258 | + |
|
| 259 | +One approach is to allow individual Haskell threads to prepare multiple I/O
|
|
| 260 | +operations by providing asynchronous I/O primops. This could be quite effective
|
|
| 261 | +at generating a lot of I/O, but it would likely see relatively little use
|
|
| 262 | +because it requires changing application designs.
|
|
| 263 | + |
|
| 264 | +Another approach is by having the scheduler not always submit pending I/O, but
|
|
| 265 | +instead let it run other threads in the hope that they will produce more
|
|
| 266 | +pending I/O. It would wait until either a time limit or a pending count limit
|
|
| 267 | +before submitting the I/O. Such an approach would increase I/O latency:
|
|
| 268 | +consider the scheduler running a thread that creates a pending I/O operation,
|
|
| 269 | +followed by running a CPU-bound thread for a whole 20 millisecond timeslice,
|
|
| 270 | +after which the scheduler submits the I/O. Thus this approach could only be
|
|
| 271 | +used for low priority I/O, which itself would require introducing a notion of
|
|
| 272 | +I/O priority.
|
|
| 273 | + |
|
| 274 | +One can imagine variations on this design such as a more sophisticated
|
|
| 275 | +scheduler predicting if the next thread to run is likely to be I/O or CPU bound
|
|
| 276 | +and using that to decide whether to flush pending I/O or to speculate on
|
|
| 277 | +accumulating more. Or the scheduler could set a timer to interrupt CPU bound
|
|
| 278 | +threads sooner if it's speculating on gathering more I/O. This could reduce
|
|
| 279 | +the cost on average, and bound latency, but there would still be a latency vs
|
|
| 280 | +throughput tradeoff, which would almost certainly require some notion of I/O
|
|
| 281 | +priority.
|
|
| 282 | + |
|
| 283 | + |
|
| 284 | +io_uring features we use
|
|
| 285 | +------------------------
|
|
| 286 | + |
|
| 287 | +IORING_FEAT_NODROP
|
|
| 288 | + |
|
| 289 | +io_uring features we could use but don't (yet)
|
|
| 290 | +----------------------------------------------
|
|
| 291 | + |
|
| 292 | +io_uring features we cannot use
|
|
| 293 | +-------------------------------
|
|
| 294 | + |
|
| 295 | +NOTES: cannot use registered ring fd, due to multiple worker threads, even
|
|
| 296 | +though it's protected by the lock. Same for registered/direct fds.
|
|
| 297 | + |
|
| 298 | + |
|
| 299 | +Tracking counters
|
|
| 300 | +-----------------
|
|
| 301 | + |
|
| 302 | +We track the number of operations submitted (by Haskell threads to the I/O
|
|
| 303 | +manager) and not yet notified of completion:
|
|
| 304 | + |
|
| 305 | +> int n_submitted_b;
|
|
| 306 | +> int n_submitted_nb;
|
|
| 307 | + |
|
| 308 | +These are incremented when the primop submits I/O to the I/O manager, and are
|
|
| 309 | +decremented when the I/O completion is processed. We track blocking and
|
|
| 310 | +non-blocking operations separately.
|
|
| 311 | + |
|
| 312 | +We track the number of operations that are prepared in the submission queue,
|
|
| 313 | +but not yet submitted to the kernel.
|
|
| 314 | + |
|
| 315 | +> int n_prepared_b;
|
|
| 316 | +> int n_prepared_nb;
|
|
| 317 | + |
|
| 318 | +These are incremented when an operation is prepared in the submission queue
|
|
| 319 | +and decremented when operations are submitted to the kernel.
|
|
| 320 | + |
|
| 321 | +We track the number of operations submitted to the kernel and where the
|
|
| 322 | +completion has not yet been processed. We call these "in-flight" operations.
|
|
| 323 | + |
|
| 324 | +> int n_inflight_b;
|
|
| 325 | +> int n_inflight_nb;
|
|
| 326 | + |
|
| 327 | +These are incremented when operations are submitted to the kernel, and
|
|
| 328 | +decremented when I/O completions are processed.
|
|
| 329 | + |
|
| 330 | +We track the limit on the number of operations the I/O manager will allow to be
|
|
| 331 | +in-flight with the kernel.
|
|
| 332 | + |
|
| 333 | +> int limit_inflight_b;
|
|
| 334 | +> int limit_inflight_nb;
|
|
| 335 | + |
|
| 336 | +These are typically set on RTS startup and then rarely changed. The
|
|
| 337 | +limit_inflight_b can be reduced dynamically if the I/O manager encounters
|
|
| 338 | +EAGAIN when submitting operations.
|
|
| 339 | + |
|
| 340 | +We track the number of non-blocking operations that are in the overflow queue.
|
|
| 341 | +These are operations that have been submitted, and could not be put into the
|
|
| 342 | +submission queue (because it would exceed the limit_inflight_nb), and so go
|
|
| 343 | +into the overflow queue instead.
|
|
| 344 | + |
|
| 345 | +> int n_overflow_nb;
|
|
| 346 | + |
|
| 347 | +This is incremented (instead of n_prepared_nb) if the number of n_inflight_nb
|
|
| 348 | +plus n_prepared_nb is at or above the limit_inflight_nb, in which case the
|
|
| 349 | +operation (and submitting thread) is put into the overflow queue. It is
|
|
| 350 | +decremented when there is space available within the limit_inflight_nb and the
|
|
| 351 | +operation can be put into the submission queue (and thus also incrementing the
|
|
| 352 | +n_prepared_nb).
|
|
| 353 | + |
|
| 354 | +We maintain two invariants:
|
|
| 355 | + |
|
| 356 | +1. n_submitted_b = n_prepared_b + n_inflight_b
|
|
| 357 | +2. n_submitted_nb = n_prepared_nb + n_overflow_nb + n_inflight_nb
|
|
| 358 | + |
|
| 359 | +The implication is that set of prepared, overflow and inflight operations are
|
|
| 360 | +distinct from each other and their union is equal to the submitted set. Another
|
|
| 361 | +way to look at it is that there distinct states for submitted operations:
|
|
| 362 | +prepared, overflow (for non-blocking) and inflight.
|
|
| 363 | + |
|
| 364 | +******************************************************************************/
|
|
| 365 | + |
|
| 366 | +/* Forward declarations */
|
|
| 367 | +static int enlargeTables(Capability *cap, CapIOManager *iomgr);
|
|
| 368 | +static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop);
|
|
| 369 | +static void enqueueOverflowQueue(Capability *cap, CapIOManager *iomgr,
|
|
| 370 | + StgTSO *tso, struct io_uring_sqe *sqe);
|
|
| 371 | +static void dequeueOverflowQueue(CapIOManager *iomgr,
|
|
| 372 | + StgTSO **ptso, struct io_uring_sqe **psqe);
|
|
| 373 | + |
|
| 374 | +/* Constants */
|
|
| 375 | + |
|
| 376 | +/* A couple tags we add to the sqe->user_data to tell us about this operation
|
|
| 377 | + * when we process the completion. Currently we distinguish:
|
|
| 378 | + * 1. non-blocking vs blocking operations, just so we can update our counters
|
|
| 379 | + * which count these separately.
|
|
| 380 | + * 2. cancellation operations
|
|
| 381 | + */
|
|
| 382 | +const uint64_t AIOP_TAG_CANCEL = 0x80lu << 56; /* bit 63 */
|
|
| 383 | +const uint64_t AIOP_TAG_OP_NB = 0x40lu << 56; /* bit 62 */
|
|
| 384 | +const uint64_t AIOP_TAG_MASK = 0xc0lu << 56; /* bit 62 | 63 */
|
|
| 385 | + |
|
| 386 | + |
|
| 387 | +void initCapabilityIOManagerURing(Capability *cap, CapIOManager *iomgr)
|
|
| 388 | +{
|
|
| 389 | + initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
|
|
| 390 | + iomgr->timeout_queue = emptyTimeoutQueue();
|
|
| 391 | + |
|
| 392 | + int sq_entries = RtsFlags.MiscFlags.io_uring_sq_entries;
|
|
| 393 | + |
|
| 394 | + iomgr->n_submitted_b = 0;
|
|
| 395 | + iomgr->n_submitted_nb = 0;
|
|
| 396 | + iomgr->n_prepared_b = 0;
|
|
| 397 | + iomgr->n_prepared_nb = 0;
|
|
| 398 | + iomgr->n_inflight_b = 0;
|
|
| 399 | + iomgr->n_inflight_nb = 0;
|
|
| 400 | + iomgr->limit_inflight_b = INT_MAX;
|
|
| 401 | + iomgr->limit_inflight_nb = 4 * sq_entries;
|
|
| 402 | + iomgr->n_overflow_nb = 0;
|
|
| 403 | + iomgr->overflow_tso_q_hd = END_TSO_QUEUE;
|
|
| 404 | + iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
|
|
| 405 | + iomgr->overflow_sqe_q_hd = NULL;
|
|
| 406 | + iomgr->overflow_sqe_q_tl = NULL;
|
|
| 407 | +
|
|
| 408 | + /* Set the uring params: we want to use independent sizes of submission
|
|
| 409 | + * and completion queues. We typically want a bigger completion queue than
|
|
| 410 | + * a submission queue.
|
|
| 411 | + */
|
|
| 412 | + struct io_uring_params params = {
|
|
| 413 | + .flags = IORING_SETUP_CQSIZE
|
|
| 414 | + | IORING_SETUP_CLAMP
|
|
| 415 | + | IORING_SETUP_SUBMIT_ALL,
|
|
| 416 | + .cq_entries = RtsFlags.MiscFlags.io_uring_cq_entries
|
|
| 417 | + };
|
|
| 418 | + //TODO: what happens if we use flags that are not recognised by the kernel
|
|
| 419 | + // version, e.g. IORING_SETUP_SUBMIT_ALL prior to 5.18?
|
|
| 420 | + |
|
| 421 | + /* TODO: see if we want to support IORING_SETUP_SQPOLL. With
|
|
| 422 | + IORING_FEAT_NATIVE_WORKERS, it doesn't need any priviledges. */
|
|
| 423 | + |
|
| 424 | + /* Share the same kernel work-queue between the urings for each capability.
|
|
| 425 | + * Do this by using the IORING_SETUP_ATTACH_WQ flag for capabilities > 0,
|
|
| 426 | + * and pass the uring fd for cap 0 (the main capability).
|
|
| 427 | + */
|
|
| 428 | + if (cap->no > 0) {
|
|
| 429 | + params.flags |= IORING_SETUP_ATTACH_WQ;
|
|
| 430 | + params.wq_fd = MainCapability.iomgr->uring->ring_fd;
|
|
| 431 | + }
|
|
| 432 | + |
|
| 433 | + /* Try to initialise the uring */
|
|
| 434 | + struct io_uring *uring = stgMallocBytes(sizeof (struct io_uring),
|
|
| 435 | + "initCapabilityIOManagerUring");
|
|
| 436 | + int res = io_uring_queue_init_params(sq_entries, uring, ¶ms);
|
|
| 437 | + if (res < 0) 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: explain why */
|
|
| 447 | + | IORING_FEAT_SUBMIT_STABLE
|
|
| 448 | + |
|
| 449 | + /* Needed for read/write that updates the file pos. */
|
|
| 450 | + | IORING_FEAT_RW_CUR_POS
|
|
| 451 | + |
|
| 452 | + /* Needed for corner cases like reading from /proc/self,
|
|
| 453 | + * or signalfd */
|
|
| 454 | + | IORING_FEAT_NATIVE_WORKERS;
|
|
| 455 | + |
|
| 456 | + if ((uring->features & required) != required) goto fail;
|
|
| 457 | + |
|
| 458 | + /* Arrange for the uring (fd and mmap'ed queues) not to be inherited. */
|
|
| 459 | + res = fcntl(uring->ring_fd, F_SETFD, FD_CLOEXEC);
|
|
| 460 | + if (res < 0) goto fail;
|
|
| 461 | + res = io_uring_ring_dontfork(uring);
|
|
| 462 | + if (res < 0) goto fail;
|
|
| 463 | + |
|
| 464 | + /* Success. Save what we need. */
|
|
| 465 | + iomgr->uring = uring;
|
|
| 466 | + return;
|
|
| 467 | + |
|
| 468 | + /* Failure. Clean up. */
|
|
| 469 | +fail:
|
|
| 470 | + stgFree(uring);
|
|
| 471 | + barf("uring iomgr: initialisation failed");
|
|
| 472 | + //TODO: we should add support to fail and use a fallback I/O manager
|
|
| 473 | +}
|
|
| 474 | + |
|
| 475 | + |
|
| 476 | +void initCapabilityIOManagerAfterForkURing(Capability *cap STG_UNUSED,
|
|
| 477 | + CapIOManager *iomgr STG_UNUSED)
|
|
| 478 | +{
|
|
| 479 | + //TODO: ugg, do we need to shutdown all the other caps? Or does that
|
|
| 480 | + // happen automagically? Need to look into forkProcess and shutting
|
|
| 481 | + // down the I/O managers.
|
|
| 482 | +}
|
|
| 483 | + |
|
| 484 | +/******************************************************************************
|
|
| 485 | + * Common prologues and epilogues for primops for I/O operations.
|
|
| 486 | + *
|
|
| 487 | + * There are different common prologues/epilogues depending on:
|
|
| 488 | + * - synchronous or asynchronous primops
|
|
| 489 | + * - blocking or non-blocking I/O operations
|
|
| 490 | + */
|
|
| 491 | + |
|
| 492 | + |
|
| 493 | +/* The common prologue for for all synchronous primops for both blocking and
|
|
| 494 | + * non-blocking I/O operations.
|
|
| 495 | + */
|
|
| 496 | +static int prologueSyncIOOp(Capability *cap, StgTSO *tso,
|
|
| 497 | + int why_blocked, StgAsyncIOOp **paiop)
|
|
| 498 | +{
|
|
| 499 | + StgAsyncIOOp *aiop;
|
|
| 500 | + aiop = (StgAsyncIOOp *)allocateMightFail(cap, sizeofW(StgAsyncIOOp));
|
|
| 501 | + if (RTS_UNLIKELY(aiop == NULL)) { return (sizeof(StgAsyncIOOp)); }
|
|
| 502 | + SET_HDR(aiop, &stg_ASYNCIOOP_info, CCS_SYSTEM); //TODO: get CCCS
|
|
| 503 | + aiop->notify_type = NotifyTSO;
|
|
| 504 | + aiop->notify.tso = tso;
|
|
| 505 | + tso->why_blocked = why_blocked;
|
|
| 506 | + tso->block_info.aiop = aiop;
|
|
| 507 | + *paiop = aiop;
|
|
| 508 | + return 0;
|
|
| 509 | +}
|
|
| 510 | + |
|
| 511 | + |
|
| 512 | +/* The common prologue for all non-blocking I/O operations.
|
|
| 513 | + *
|
|
| 514 | + * Allocate a table index
|
|
| 515 | + * Fill in some of the aiop fields
|
|
| 516 | + * Allocate an SQE, either on the ring or on the heap if we're in overflow.
|
|
| 517 | + */
|
|
| 518 | +static int prologueNonBlockingIOOp(Capability *cap,
|
|
| 519 | + StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 520 | + struct io_uring_sqe **psqe,
|
|
| 521 | + bool *tso_block)
|
|
| 522 | +{
|
|
| 523 | + CapIOManager *iomgr = cap->iomgr;
|
|
| 524 | + if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
|
|
| 525 | + int fail = enlargeTables(cap, iomgr);
|
|
| 526 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 527 | + }
|
|
| 528 | + |
|
| 529 | + int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
|
|
| 530 | + |
|
| 531 | + aiop->capno = cap->no;
|
|
| 532 | + aiop->index = index;
|
|
| 533 | + |
|
| 534 | + struct io_uring_sqe *sqe;
|
|
| 535 | + |
|
| 536 | + if (iomgr->n_inflight_nb + iomgr->n_prepared_nb
|
|
| 537 | + < iomgr->limit_inflight_nb) {
|
|
| 538 | + /* The typical case. Allocate an SQE from the ring */
|
|
| 539 | + sqe = io_uring_get_sqe(iomgr->uring);
|
|
| 540 | + ASSERT(sqe); /* Otherwise we counted wrong */
|
|
| 541 | + iomgr->n_submitted_nb++;
|
|
| 542 | + iomgr->n_prepared_nb++;
|
|
| 543 | + *tso_block = false;
|
|
| 544 | + } else {
|
|
| 545 | + /* We're going to have to block the submitting thread.
|
|
| 546 | + * Allocate an SQE on the heap and suspend the calling TSO.
|
|
| 547 | + */
|
|
| 548 | + sqe = stgMallocBytes(sizeof (struct io_uring_sqe), "uring iomgr");
|
|
| 549 | + enqueueOverflowQueue(cap, iomgr, tso, sqe);
|
|
| 550 | + iomgr->n_submitted_nb++;
|
|
| 551 | + iomgr->n_overflow_nb++;
|
|
| 552 | + *tso_block = true;
|
|
| 553 | + }
|
|
| 554 | + io_uring_sqe_set_data64(sqe, index);
|
|
| 555 | + *psqe = sqe;
|
|
| 556 | + return 0;
|
|
| 557 | +}
|
|
| 558 | + |
|
| 559 | + |
|
| 560 | +/* The common prologue for all blocking I/O operations.
|
|
| 561 | + *
|
|
| 562 | + * Allocate a table index
|
|
| 563 | + * Fill in some of the aiop fields
|
|
| 564 | + * Allocate an SQE, or return failure if we're at the limit.
|
|
| 565 | + */
|
|
| 566 | +static int prologueBlockingIOOp(Capability *cap,
|
|
| 567 | + StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 568 | + struct io_uring_sqe **psqe)
|
|
| 569 | +{
|
|
| 570 | + CapIOManager *iomgr = cap->iomgr;
|
|
| 571 | + |
|
| 572 | + if (iomgr->n_inflight_b + iomgr->n_prepared_b >= iomgr->limit_inflight_b) {
|
|
| 573 | + /* If we reach the limit we fail and throw an exception */
|
|
| 574 | + raiseAsync(cap, tso, (StgClosure *)blockedOnBadFD_closure
|
|
| 575 | + /*TODO: use ioopResourcesExhausted_closure */,
|
|
| 576 | + false, NULL);
|
|
| 577 | + return -1;
|
|
| 578 | + //TODO: review this
|
|
| 579 | + //TODO: current return value is for memory alloc failure, not for
|
|
| 580 | + // other failures. Need error result separate from alloc.
|
|
| 581 | + }
|
|
| 582 | + |
|
| 583 | + if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
|
|
| 584 | + int fail = enlargeTables(cap, iomgr);
|
|
| 585 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 586 | + }
|
|
| 587 | + |
|
| 588 | + int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
|
|
| 589 | + |
|
| 590 | + aiop->capno = cap->no;
|
|
| 591 | + aiop->index = index;
|
|
| 592 | + |
|
| 593 | + /* Allocate an SQE on the ring */
|
|
| 594 | + struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
|
|
| 595 | + ASSERT(sqe); /* Otherwise we counted wrong */
|
|
| 596 | + iomgr->n_submitted_b++;
|
|
| 597 | + iomgr->n_prepared_b++;
|
|
| 598 | + io_uring_sqe_set_data64(sqe, index);
|
|
| 599 | + *psqe = sqe;
|
|
| 600 | + return 0;
|
|
| 601 | +}
|
|
| 602 | + |
|
| 603 | + |
|
| 604 | +/* The common epilogue for all async non-blocking I/O operations.
|
|
| 605 | + */
|
|
| 606 | +static int epilogueAsyncNonBlockingIOOp(CapIOManager *iomgr,
|
|
| 607 | + StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 608 | + bool tso_block)
|
|
| 609 | +{
|
|
| 610 | + if (tso_block) {
|
|
| 611 | + tso->why_blocked = BlockedOnIOSubmission;
|
|
| 612 | + tso->block_info.aiop = aiop;
|
|
| 613 | + return -1;
|
|
| 614 | + } else if (io_uring_sq_space_left(iomgr->uring) == 0) {
|
|
| 615 | + return -1;
|
|
| 616 | + } else {
|
|
| 617 | + return 0;
|
|
| 618 | + }
|
|
| 619 | +}
|
|
| 620 | + |
|
| 621 | + |
|
| 622 | +/* Common epilogue for all async blocking I/O operations.
|
|
| 623 | + */
|
|
| 624 | +static int epilogueAsyncBlockingIOOp(CapIOManager *iomgr)
|
|
| 625 | +{
|
|
| 626 | + return io_uring_sq_space_left(iomgr->uring) == 0 ? -1 : 0;
|
|
| 627 | +}
|
|
| 628 | + |
|
| 629 | + |
|
| 630 | +/******************************************************************************
|
|
| 631 | + * Non-blocking I/O operations. This includes read/write on files (not sockets).
|
|
| 632 | + *
|
|
| 633 | + * The code is organised as common I/O preparation functions and then individual
|
|
| 634 | + * primops (sync and async). They also rely on the common prologue and epilogue
|
|
| 635 | + * functions above.
|
|
| 636 | + */
|
|
| 637 | + |
|
| 638 | +static int prepareIOReadWrite(Capability *cap, StgTSO *tso, bool *tso_block,
|
|
| 639 | + StgAsyncIOOp *aiop,
|
|
| 640 | + IOReadOrWrite rw, int fd,
|
|
| 641 | + StgClosure *live, void *buf,
|
|
| 642 | + size_t len, off_t off) {
|
|
| 643 | + struct io_uring_sqe *sqe;
|
|
| 644 | + int fail = prologueNonBlockingIOOp(cap, tso, aiop, &sqe, tso_block);
|
|
| 645 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 646 | + |
|
| 647 | + aiop->live = live;
|
|
| 648 | + if (rw == IORead) {
|
|
| 649 | + io_uring_prep_read(sqe, fd, buf, len, off);
|
|
| 650 | + } else {
|
|
| 651 | + io_uring_prep_write(sqe, fd, buf, len, off);
|
|
| 652 | + }
|
|
| 653 | + return 0;
|
|
| 654 | +}
|
|
| 655 | + |
|
| 656 | + |
|
| 657 | +int asyncIOReadWriteURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 658 | + IOReadOrWrite rw, int fd,
|
|
| 659 | + StgClosure *live, void *buf,
|
|
| 660 | + size_t len, off_t off)
|
|
| 661 | +{
|
|
| 662 | + bool tso_block;
|
|
| 663 | + int fail = prepareIOReadWrite(cap, tso, &tso_block, aiop,
|
|
| 664 | + rw, fd, live, buf, len, off);
|
|
| 665 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 666 | + |
|
| 667 | + return epilogueAsyncNonBlockingIOOp(cap->iomgr, tso, aiop, tso_block);
|
|
| 668 | +}
|
|
| 669 | + |
|
| 670 | + |
|
| 671 | +int syncIOReadWriteURing(Capability *cap, StgTSO *tso,
|
|
| 672 | + IOReadOrWrite rw, int fd,
|
|
| 673 | + StgClosure *live, void *buf,
|
|
| 674 | + size_t len, off_t off)
|
|
| 675 | +{
|
|
| 676 | + StgAsyncIOOp *aiop;
|
|
| 677 | + int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 678 | + int fail = prologueSyncIOOp(cap, tso, why_blocked, &aiop);
|
|
| 679 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 680 | + |
|
| 681 | + bool unused;
|
|
| 682 | + fail = prepareIOReadWrite(cap, tso, &unused, aiop,
|
|
| 683 | + rw, fd, live, buf, len, off);
|
|
| 684 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 685 | + return 0;
|
|
| 686 | +}
|
|
| 687 | + |
|
| 688 | + |
|
| 689 | +/******************************************************************************
|
|
| 690 | + * Blocking I/O operations. This includes waiting for I/O readiness on sockets,
|
|
| 691 | + * pipes etc.
|
|
| 692 | + *
|
|
| 693 | + * The code is organised as common I/O preparation functions and then individual
|
|
| 694 | + * primops (sync and async). They also rely on the common prologue and epilogue
|
|
| 695 | + * functions above.
|
|
| 696 | + */
|
|
| 697 | + |
|
| 698 | + |
|
| 699 | +static void prepareIOWaitReady(StgAsyncIOOp *aiop, struct io_uring_sqe *sqe,
|
|
| 700 | + IOReadOrWrite rw, int fd) {
|
|
| 701 | + aiop->live = &stg_ASYNCIO_LIVE0_closure;
|
|
| 702 | + io_uring_prep_poll_add(sqe, fd, rw == IORead ? POLLIN : POLLOUT);
|
|
| 703 | +}
|
|
| 704 | + |
|
| 705 | + |
|
| 706 | +int asyncIOWaitReadyURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 707 | + IOReadOrWrite rw, int fd)
|
|
| 708 | +{
|
|
| 709 | + struct io_uring_sqe *sqe;
|
|
| 710 | + int fail = prologueBlockingIOOp(cap, tso, aiop, &sqe);
|
|
| 711 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 712 | + |
|
| 713 | + prepareIOWaitReady(aiop,sqe, rw, fd);
|
|
| 714 | + |
|
| 715 | + return epilogueAsyncBlockingIOOp(cap->iomgr);
|
|
| 716 | +}
|
|
| 717 | + |
|
| 718 | + |
|
| 719 | +int syncIOWaitReadyURing(Capability *cap, StgTSO *tso,
|
|
| 720 | + IOReadOrWrite rw, int fd)
|
|
| 721 | +{
|
|
| 722 | + StgAsyncIOOp *aiop;
|
|
| 723 | + int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 724 | + int fail = prologueSyncIOOp(cap, tso, why_blocked, &aiop);
|
|
| 725 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 726 | + |
|
| 727 | + struct io_uring_sqe *sqe;
|
|
| 728 | + fail = prologueBlockingIOOp(cap, tso, aiop, &sqe);
|
|
| 729 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 730 | + |
|
| 731 | + prepareIOWaitReady(aiop, sqe, rw, fd);
|
|
| 732 | + |
|
| 733 | + return 0;
|
|
| 734 | +}
|
|
| 735 | + |
|
| 736 | + |
|
| 737 | +/******************************************************************************
|
|
| 738 | + * Actions to cancel outstanding I/O operations. Also support cancelling any
|
|
| 739 | + * outstanding I/O on an fd prior to it being closed.
|
|
| 740 | + *
|
|
| 741 | + * This covers both synchronous and asynchronous operations.
|
|
| 742 | + */
|
|
| 743 | + |
|
| 744 | + |
|
| 745 | +static void ioCancel(Capability *cap, StgAsyncIOOp *aiop);
|
|
| 746 | + |
|
| 747 | + |
|
| 748 | +void syncIOCancelURing(Capability *cap, StgTSO *tso)
|
|
| 749 | +{
|
|
| 750 | + StgAsyncIOOp *aiop = tso->block_info.aiop;
|
|
| 751 | + ASSERT(aiop->notify_type == NotifyTSO);
|
|
| 752 | + ASSERT(indexClosureTable(&cap->iomgr->aiop_table, aiop->index) == aiop);
|
|
| 753 | + ioCancel(cap, aiop);
|
|
| 754 | + /* We cannot use the normal notifyIOCompletion here. We are in the context
|
|
| 755 | + * of throwTo, interrupting a thread blocked on IO via an async exception.
|
|
| 756 | + * We don't put the TSO back on the run queue or change the why_blocked
|
|
| 757 | + * status, as that is done by removeFromQueues (in the throwTo* functions).
|
|
| 758 | + */
|
|
| 759 | + tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 760 | + |
|
| 761 | + //TODO: Synchronous cancellation from throwTo seems to be pretty keen for
|
|
| 762 | + // the thread to be unblocked immediately so it can start unwinding the
|
|
| 763 | + // stack. Perhaps it is ok to continue the cancellation in the background.
|
|
| 764 | + // But if so then we will need to adjust the notify type to be none /
|
|
| 765 | + // cancelled!
|
|
| 766 | + /* Cancelling thread-synchronous I/O happens from throwTo, which is very
|
|
| 767 | + * keen for the thread to be unblocked immediately so it can unwind the
|
|
| 768 | + * stack and schedule the thread to run an exception handler. This demand
|
|
| 769 | + * to be synchronous is a bit tricky to arrange because cancelling the I/O
|
|
| 770 | + * operation the thread is blocked on is potentially asynchronous.
|
|
| 771 | + */
|
|
| 772 | + aiop->notify_type = NotifyNone;
|
|
| 773 | + aiop->notify.tso = END_TSO_QUEUE;
|
|
| 774 | +}
|
|
| 775 | + |
|
| 776 | + |
|
| 777 | +void asyncIOCancelURing(Capability *cap, StgAsyncIOOp *aiop)
|
|
| 778 | +{
|
|
| 779 | + /* We can reliably determine if the aiop is still in progress by checking
|
|
| 780 | + * if the aiop_table still points to this aiop object. This is reliable
|
|
| 781 | + * because each aiop is GC heap allocated, so cannot be recycled until it
|
|
| 782 | + * is no longer retained by the application.
|
|
| 783 | + */
|
|
| 784 | + ASSERT(aiop->notify_type != NotifyTSO);
|
|
| 785 | + if (indexClosureTable(&cap->iomgr->aiop_table, aiop->index) == aiop) {
|
|
| 786 | + ioCancel(cap, aiop);
|
|
| 787 | + notifyIOCompletion(cap, aiop);
|
|
| 788 | + }
|
|
| 789 | +}
|
|
| 790 | + |
|
| 791 | + |
|
| 792 | +static void ioCancel(Capability *cap, StgAsyncIOOp *aiop)
|
|
| 793 | +{
|
|
| 794 | + barf("URing.c:ioCancel:TODO");
|
|
| 795 | + |
|
| 796 | + CapIOManager *iomgr = cap->iomgr;
|
|
| 797 | + |
|
| 798 | + int ix = aiop->index;
|
|
| 799 | + struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
|
|
| 800 | + //TODO: return status to indicate if we need to return to the scheduler
|
|
| 801 | + // to flush a full submission queue
|
|
| 802 | + |
|
| 803 | + /* io_uring lets us include one word into submission queue entries (SQEs),
|
|
| 804 | + * which come back in the corresponding completion queue entry (CQE). We
|
|
| 805 | + * use this feature to identify the aiop so that we will be able to
|
|
| 806 | + * process the completion properly, e.g. waking up the right thread.
|
|
| 807 | + * We can't use a direct pointer to the aiop because the aiops are heap
|
|
| 808 | + * allocated and GC pointers are not stable. We use the index in the
|
|
| 809 | + * ClosureTable, because this is stable. Indeed the raison d'être of the
|
|
| 810 | + * ClosureTable is to provide stable pointers for thus purpose.
|
|
| 811 | + //TODO: the above is a helpful comment but it belongs elsewhere, e.g.
|
|
| 812 | + //in the intro.
|
|
| 813 | + */
|
|
| 814 | + uint64_t sqe_data = ix;
|
|
| 815 | + |
|
| 816 | + /* Although IORING_OP_POLL_ADD has a special separate cancellation using
|
|
| 817 | + * IORING_OP_POLL_REMOVE, apparently it can also be cancelled using the
|
|
| 818 | + * generic IORING_OP_ASYNC_CANCEL, which is good so we don't need to
|
|
| 819 | + * distinguish.
|
|
| 820 | + */
|
|
| 821 | + io_uring_prep_cancel64(sqe, sqe_data, 0 /*flags*/);
|
|
| 822 | + //TODO: if we use other tags in the sqe_data we'll need to reconstruct
|
|
| 823 | + // them here so we can find the right item. e.g. if we use AIOP_TAG_OP_NB
|
|
| 824 | + // we'd need to distinguish in the aiop->flags for example.
|
|
| 825 | + |
|
| 826 | + /* Cancelling is itself a new uring I/O operation which will have a
|
|
| 827 | + * corresponding completion. Set a high bit to mark this as a cancel
|
|
| 828 | + * operation, but still knowing the index of the original operation.
|
|
| 829 | + */
|
|
| 830 | + io_uring_sqe_set_data64(sqe, sqe_data | AIOP_TAG_CANCEL);
|
|
| 831 | + |
|
| 832 | + //TODO: verify for IORING_OP_POLL_ADD, if we do a successful
|
|
| 833 | + // IORING_OP_POLL_REMOVE then which completions do we get?
|
|
| 834 | + // Do we get a completion for the IORING_OP_POLL_ADD, and if so with
|
|
| 835 | + // what result? Or does a successful IORING_OP_POLL_REMOVE mean we
|
|
| 836 | + // only get a completion for the remove and not the add?
|
|
| 837 | + |
|
| 838 | + //TODO: verify similar for normal I/O cancel, e.g. a read on a pipe.
|
|
| 839 | + // Which completions do we get if we
|
|
| 840 | + |
|
| 841 | + //TODO: is it ok cancel I/O asynchronously here? Do we need to submit the
|
|
| 842 | + // cancel op? The cancel op will complete synchronously but the cancellation
|
|
| 843 | + // may only complete later. This might confuse resource cleanup, e.g. because
|
|
| 844 | + // a file will not get closed until the cancel finishes.
|
|
| 845 | +
|
|
| 846 | + /* This is an CQE for a cancellation. After posting a
|
|
| 847 | + * cancellation SQE then we expect to get _both_
|
|
| 848 | + * a cancellation CQE and a CQE for the original operation that
|
|
| 849 | + * was the target of cancellation. This means that (provided
|
|
| 850 | + * it's not an error) we can ignore the cancellation SQE and
|
|
| 851 | + * just process the normal SQE for the target operation.
|
|
| 852 | + *
|
|
| 853 | + * The target operation will either be cancelled successfully
|
|
| 854 | + * immediately (in which case the cancellation cqe_data->res == 0) or if the target operation
|
|
| 855 | + * is in progress and cannot be cancelled, then we'll get
|
|
| 856 | + * cqe_data->res == -EALREADY for the cancellation
|
|
| 857 | + * the
|
|
| 858 | + * result of the operation (which may still be an interrupted
|
|
| 859 | + * outcome. If the cancellation fails, we
|
|
| 860 | + */
|
|
| 861 | + if (cqe_data->res != 0 && cqe_data->res != -EALREADY)
|
|
| 862 | + sysErrorBelch("uring:processIOCompletions");
|
|
| 863 | + }
|
|
| 864 | + |
|
| 865 | +}
|
|
| 866 | + |
|
| 867 | + |
|
| 868 | + |
|
| 869 | + |
|
| 870 | +/* TODO: need to add support for closing properly.
|
|
| 871 | + * Unfortunately, uring's behaviour for closing a fd when there are outstanding
|
|
| 872 | + * poll (or other async I/O) operations on that fd is unhelpful. The poll
|
|
| 873 | + * operation itself keeps a reference to the file open. Thus the close will
|
|
| 874 | + * not in fact interrupt and cancel the poll.
|
|
| 875 | + * So the I/O manager needs to be notified of fd close, so that we can do
|
|
| 876 | + * something. Fortunately we can use io_uring_prep_cancel_fd to cancel all
|
|
| 877 | + * operations on an fd.
|
|
| 878 | + *
|
|
| 879 | + * I think cancelled ops _do_ generate CQEs. So we should be able to do the
|
|
| 880 | + * appropriate notifications by waiting for the original CQEs. We should
|
|
| 881 | + * probably issue the cancellation
|
|
| 882 | + *
|
|
| 883 | + */
|
|
| 884 | + |
|
| 885 | + |
|
| 886 | +/******************************************************************************
|
|
| 887 | + * The functions called from the scheduler to poll or wait for pending I/O,
|
|
| 888 | + * and process any I/O completions.
|
|
| 889 | + */
|
|
| 890 | + |
|
| 891 | + |
|
| 892 | +bool anyPendingTimeoutsOrIOURing(CapIOManager *iomgr)
|
|
| 893 | +{
|
|
| 894 | + return !isEmptyTimeoutQueue(iomgr->timeout_queue)
|
|
| 895 | + || !isEmptyClosureTable(&iomgr->aiop_table);
|
|
| 896 | +}
|
|
| 897 | + |
|
| 898 | + |
|
| 899 | +static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop)
|
|
| 900 | +{
|
|
| 901 | + switch (aiop->notify_type) {
|
|
| 902 | + case NotifyTSO:
|
|
| 903 | + {
|
|
| 904 | + if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
|
|
| 905 | + /* The fd is invalid: raise an IOError exception in the blocked
|
|
| 906 | + * thread. (See bug #4934 for what happens without this.)
|
|
| 907 | + */
|
|
| 908 | + StgTSO *tso = aiop->notify.tso;
|
|
| 909 | + debugTrace(DEBUG_iomanager,
|
|
| 910 | + "Raising exception in thread %" FMT_StgThreadID
|
|
| 911 | + " blocked on an invalid fd", tso->id);
|
|
| 912 | + raiseAsync(cap, tso, (StgClosure *)blockedOnBadFD_closure,
|
|
| 913 | + false, NULL);
|
|
| 914 | + break;
|
|
| 915 | + } else {
|
|
| 916 | + /* We should be guaranteed that the tso is still on the same
|
|
| 917 | + * cap because the tso was not on the run queue of any cap and
|
|
| 918 | + * so is not subject to thread migration.
|
|
| 919 | + */
|
|
| 920 | + StgTSO *tso = aiop->notify.tso;
|
|
| 921 | + tso->why_blocked = NotBlocked;
|
|
| 922 | + tso->_link = END_TSO_QUEUE;
|
|
| 923 | + pushOnRunQueue(cap, tso);
|
|
| 924 | + }
|
|
| 925 | + break;
|
|
| 926 | + }
|
|
| 927 | + case NotifyMVar:
|
|
| 928 | + performTryPutMVar(cap, aiop->notify.mvar, Unit_closure);
|
|
| 929 | + break;
|
|
| 930 | + |
|
| 931 | + case NotifyTVar:
|
|
| 932 | + barf("uring iomgr: TVar notification not yet supported");
|
|
| 933 | + break;
|
|
| 934 | + }
|
|
| 935 | +}
|
|
| 936 | + |
|
| 937 | + |
|
| 938 | +/* Process all the I/O completions that are currently available without
|
|
| 939 | + * blocking.
|
|
| 940 | + *
|
|
| 941 | + * This will correctly deal with completion queue overflow: if the completions
|
|
| 942 | + * queue is empty but has the overflow bit set then io_uring_peek_batch_cqe
|
|
| 943 | + * will do another non-blocking uring enter to refill the completion queue.
|
|
| 944 | + */
|
|
| 945 | +static void processIOCompletions(Capability *cap, CapIOManager *iomgr)
|
|
| 946 | +{
|
|
| 947 | + struct io_uring *uring = iomgr->uring;
|
|
| 948 | + while (1) {
|
|
| 949 | + unsigned head, count = 0;
|
|
| 950 | + struct io_uring_cqe *cqe;
|
|
| 951 | + io_uring_for_each_cqe(uring, head, cqe) {
|
|
| 952 | + uint64_t cqe_data = io_uring_cqe_get_data64(cqe);
|
|
| 953 | + if (RTS_UNLIKELY(cqe_data & AIOP_TAG_CANCEL)) {
|
|
| 954 | + |
|
| 955 | + } else {
|
|
| 956 | + int ix = cqe_data;
|
|
| 957 | + StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, ix);
|
|
| 958 | + removeClosureTable(cap, &iomgr->aiop_table, ix);
|
|
| 959 | + aiop->result = cqe->res;
|
|
| 960 | + //TODO: if we use these tags in the sqe_data we'll need to reconstruct
|
|
| 961 | + // them for cancel so we can find the right item. e.g. if we use AIOP_TAG_OP_NB
|
|
| 962 | + // we'd need to distinguish in the aiop->flags for example.
|
|
| 963 | + if (cqe_data & AIOP_TAG_OP_NB) {
|
|
| 964 | + iomgr->n_inflight_nb--;
|
|
| 965 | + iomgr->n_submitted_nb--;
|
|
| 966 | + } else {
|
|
| 967 | + iomgr->n_inflight_b--;
|
|
| 968 | + iomgr->n_submitted_b--;
|
|
| 969 | + }
|
|
| 970 | + notifyIOCompletion(cap, aiop);
|
|
| 971 | + }
|
|
| 972 | + count++;
|
|
| 973 | + }
|
|
| 974 | + io_uring_cq_advance(uring, count);
|
|
| 975 | + if (RTS_UNLIKELY(io_uring_cq_has_overflow(uring))) {
|
|
| 976 | + if (io_uring_get_events(uring) < 0) {
|
|
| 977 | + sysErrorBelch("io_uring_enter");
|
|
| 978 | + stg_exit(EXIT_FAILURE);
|
|
| 979 | + }
|
|
| 980 | + continue;
|
|
| 981 | + } else {
|
|
| 982 | + break;
|
|
| 983 | + }
|
|
| 984 | + }
|
|
| 985 | +}
|
|
| 986 | + |
|
| 987 | + |
|
| 988 | +/* Check invariants that must hold on entry to and exit from the scheduler.
|
|
| 989 | + * Used before/after {poll,await}CompletedTimeoutsOrIOURing which are called
|
|
| 990 | + * from the scheduler.
|
|
| 991 | + */
|
|
| 992 | +static void assertURingSchedulerInvariants(CapIOManager *iomgr)
|
|
| 993 | +{
|
|
| 994 | + struct io_uring *uring = iomgr->uring;
|
|
| 995 | + |
|
| 996 | + // That our tracking counters are consistent.
|
|
| 997 | + ASSERT(iomgr->n_submitted_b == iomgr->n_prepared_b
|
|
| 998 | + + iomgr->n_inflight_b);
|
|
| 999 | + ASSERT(iomgr->n_submitted_nb == iomgr->n_prepared_nb
|
|
| 1000 | + + iomgr->n_overflow_nb
|
|
| 1001 | + + iomgr->n_inflight_nb);
|
|
| 1002 | + |
|
| 1003 | + // That we are within limits
|
|
| 1004 | + ASSERT(iomgr->n_inflight_b <= iomgr->limit_inflight_b);
|
|
| 1005 | + ASSERT(iomgr->n_inflight_nb <= iomgr->limit_inflight_nb);
|
|
| 1006 | + |
|
| 1007 | + // That we correctly track the submission queue size.
|
|
| 1008 | + ASSERT((int)io_uring_sq_ready(uring) == iomgr->n_prepared_b
|
|
| 1009 | + + iomgr->n_prepared_nb);
|
|
| 1010 | + |
|
| 1011 | + // That our overflow queue is consistent with the overflow counter.
|
|
| 1012 | + ASSERT(iomgr->n_overflow_nb > 0
|
|
| 1013 | + ? iomgr->overflow_sqe_q_hd == NULL &&
|
|
| 1014 | + iomgr->overflow_sqe_q_tl == NULL &&
|
|
| 1015 | + iomgr->overflow_tso_q_hd == END_TSO_QUEUE &&
|
|
| 1016 | + iomgr->overflow_tso_q_tl == END_TSO_QUEUE
|
|
| 1017 | + : iomgr->n_overflow_nb == 0 &&
|
|
| 1018 | + iomgr->overflow_sqe_q_hd != NULL &&
|
|
| 1019 | + iomgr->overflow_sqe_q_tl != NULL &&
|
|
| 1020 | + iomgr->overflow_tso_q_hd != END_TSO_QUEUE &&
|
|
| 1021 | + iomgr->overflow_tso_q_tl != END_TSO_QUEUE);
|
|
| 1022 | +}
|
|
| 1023 | + |
|
| 1024 | +/* If there are any completed I/O operations or expired timers, process the
|
|
| 1025 | + * completions as appropriate. If there are none, return without waiting.
|
|
| 1026 | + *
|
|
| 1027 | + * This is the non-blocking variant. See awaitCompletedTimeoutsOrIOURing
|
|
| 1028 | + * for the potentially-blocking variant.
|
|
| 1029 | + */
|
|
| 1030 | +void pollCompletedTimeoutsOrIOURing(Capability *cap)
|
|
| 1031 | +{
|
|
| 1032 | + CapIOManager *iomgr = cap->iomgr;
|
|
| 1033 | + struct io_uring *uring = iomgr->uring;
|
|
| 1034 | + |
|
| 1035 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1036 | + |
|
| 1037 | + /* Process timeouts, if any, but don't immediately return to the scheduler,
|
|
| 1038 | + * since we should submit I/O and reap any completions too.
|
|
| 1039 | + */
|
|
| 1040 | + if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
|
|
| 1041 | + Time now = getProcessElapsedTime();
|
|
| 1042 | + processTimeoutCompletions(cap, now);
|
|
| 1043 | + }
|
|
| 1044 | + |
|
| 1045 | + /* Submit I/O if needed */
|
|
| 1046 | + if (io_uring_sq_ready(uring)) {
|
|
| 1047 | + int res = io_uring_submit_and_get_events(uring);
|
|
| 1048 | + |
|
| 1049 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 1050 | + if (res == -EBUSY) {
|
|
| 1051 | + /* This is an odd one. According to the doc:
|
|
| 1052 | + * If the IORING_FEAT_NODROP feature flag is set, then EBUSY
|
|
| 1053 | + * will be returned if there were overflow entries,
|
|
| 1054 | + * IORING_ENTER_GETEVENTS flag is set and not all of the
|
|
| 1055 | + * overflow entries were able to be flushed to the CQ ring.
|
|
| 1056 | + *
|
|
| 1057 | + * So it's not really an error at all. It just means we will
|
|
| 1058 | + * have to do multiple iterations in processIOCompletions()
|
|
| 1059 | + * to collect all the completions.
|
|
| 1060 | + *
|
|
| 1061 | + * Thus EBUSY should imply that there are entries in the CQ.
|
|
| 1062 | + */
|
|
| 1063 | + ASSERT(io_uring_cq_ready(uring) > 0);
|
|
| 1064 | + }
|
|
| 1065 | + } else {
|
|
| 1066 | + ASSERT(res == iomgr->n_prepared_b + iomgr->n_prepared_nb);
|
|
| 1067 | + /* We're using IORING_SETUP_SUBMIT_ALL so we should expect to have
|
|
| 1068 | + * all of them submitted, or an error.
|
|
| 1069 | + * https://github.com/axboe/liburing/issues/186
|
|
| 1070 | + * Alternatively, we could loop and submit the remainder.
|
|
| 1071 | + */
|
|
| 1072 | + iomgr->n_inflight_b += iomgr->n_prepared_b;
|
|
| 1073 | + iomgr->n_inflight_nb += iomgr->n_prepared_nb;
|
|
| 1074 | + iomgr->n_prepared_b = 0;
|
|
| 1075 | + iomgr->n_prepared_nb = 0;
|
|
| 1076 | + }
|
|
| 1077 | + }
|
|
| 1078 | + |
|
| 1079 | + if (io_uring_cq_ready(uring)) {
|
|
| 1080 | + processIOCompletions(cap, iomgr);
|
|
| 1081 | + }
|
|
| 1082 | + //TODO: now we need to check if we have any items in our overflow queue
|
|
| 1083 | + //and if so, we need to copy some of those into the SQ and submit them.
|
|
| 1084 | + //copy in up to either the SQ limit or in-flight limit.
|
|
| 1085 | + |
|
| 1086 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1087 | +}
|
|
| 1088 | + |
|
| 1089 | + |
|
| 1090 | +/* If there are any completed I/O operations or expired timers, process the
|
|
| 1091 | + * completions as appropriate. If there are none, wait until I/O or a timer
|
|
| 1092 | + * does complete (or we get a signal with a handler) and process the
|
|
| 1093 | + * completions as appropriate.
|
|
| 1094 | + *
|
|
| 1095 | + * This is the potentially-blocking variant. See pollCompletedTimeoutsOrIOURing
|
|
| 1096 | + * for the non-blocking variant.
|
|
| 1097 | + */
|
|
| 1098 | +void awaitCompletedTimeoutsOrIOURing(Capability *cap)
|
|
| 1099 | +{
|
|
| 1100 | + CapIOManager *iomgr = cap->iomgr;
|
|
| 1101 | + struct io_uring *uring = iomgr->uring;
|
|
| 1102 | + |
|
| 1103 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1104 | + |
|
| 1105 | + do {
|
|
| 1106 | + |
|
| 1107 | + /* We're being asked (by the scheduler) to block if there's no
|
|
| 1108 | + * immediate timer or I/O completions. So there had better be
|
|
| 1109 | + * some pending I/O or pending timers, or we'd deadlock.
|
|
| 1110 | + */
|
|
| 1111 | + ASSERT(!isEmptyTimeoutQueue(iomgr->timeout_queue) ||
|
|
| 1112 | + !isEmptyClosureTable(&iomgr->aiop_table));
|
|
| 1113 | + |
|
| 1114 | + Time now = getProcessElapsedTime();
|
|
| 1115 | + processTimeoutCompletions(cap, now);
|
|
| 1116 | + |
|
| 1117 | + /* If we didn't wake any threads due to expiring timeouts, then we need
|
|
| 1118 | + * to wait on I/O. Or to put it another way, even if we did wake some
|
|
| 1119 | + * threads, we'll still poll (but not wait) for I/O. This is to ensure
|
|
| 1120 | + * we avoid starving threads blocked on I/O.
|
|
| 1121 | + */
|
|
| 1122 | + bool wait = emptyRunQueue(cap);
|
|
| 1123 | + |
|
| 1124 | + /* There are four possible cases:
|
|
| 1125 | + * 1. non-blocking check for I/O completion with I/O submission
|
|
| 1126 | + * 2. non-blocking check for I/O completion with no I/O submission
|
|
| 1127 | + * 3. blocking wait for I/O completion with a timeout
|
|
| 1128 | + * 4. blocking wait for I/O completion without a timeout
|
|
| 1129 | + *
|
|
| 1130 | + * If we woke any threads due to timeouts we're in the first or second
|
|
| 1131 | + * case.
|
|
| 1132 | + *
|
|
| 1133 | + * Otherwise we're in one of the blocking cases. We will use a timeout
|
|
| 1134 | + * if the timeout queue is non-empty.
|
|
| 1135 | + */
|
|
| 1136 | + |
|
| 1137 | + int res;
|
|
| 1138 | + if (!wait && io_uring_sq_ready(uring)) {
|
|
| 1139 | + /* Case 1 (as above) */
|
|
| 1140 | + res = io_uring_submit_and_get_events(uring);
|
|
| 1141 | + } else if (!wait) {
|
|
| 1142 | + /* Case 2 (as above) */
|
|
| 1143 | + res = io_uring_get_events(uring);
|
|
| 1144 | + } else {
|
|
| 1145 | + struct timespec tv;
|
|
| 1146 | + if (timeoutInNanoseconds(iomgr, true, now, &tv)) {
|
|
| 1147 | + /* Case 3 (as above) */
|
|
| 1148 | + /* struct timespec and struct __kernel_timespec are compatible
|
|
| 1149 | + * but not exactly the same. Sigh. */
|
|
| 1150 | + struct __kernel_timespec ts = { .tv_sec = tv.tv_sec,
|
|
| 1151 | + .tv_nsec = tv.tv_nsec };
|
|
| 1152 | + struct io_uring_cqe *cqe_unused;
|
|
| 1153 | + res = io_uring_submit_and_wait_timeout(uring, &cqe_unused,
|
|
| 1154 | + 1, &ts, NULL);
|
|
| 1155 | + } else {
|
|
| 1156 | + /* Case 4 (as above) */
|
|
| 1157 | + res = io_uring_submit_and_wait(uring, 1);
|
|
| 1158 | + }
|
|
| 1159 | + }
|
|
| 1160 | +
|
|
| 1161 | + if (res >= 0) {
|
|
| 1162 | + processIOCompletions(cap, iomgr);
|
|
| 1163 | + } else if (errno == EINTR) {
|
|
| 1164 | + |
|
| 1165 | + } else if (errno == EBUSY || errno == EAGAIN) {
|
|
| 1166 | + |
|
| 1167 | + } else {
|
|
| 1168 | + sysErrorBelch("io_uring_enter");
|
|
| 1169 | + stg_exit(EXIT_FAILURE);
|
|
| 1170 | + }
|
|
| 1171 | + } while (emptyRunQueue(cap)
|
|
| 1172 | + && (!isEmptyClosureTable(&iomgr->aiop_table) ||
|
|
| 1173 | + !isEmptyTimeoutQueue(iomgr->timeout_queue))
|
|
| 1174 | + && getSchedState() == SCHED_RUNNING);
|
|
| 1175 | + |
|
| 1176 | + assertURingSchedulerInvariants(iomgr);
|
|
| 1177 | +}
|
|
| 1178 | + |
|
| 1179 | + |
|
| 1180 | +/******************************************************************************
|
|
| 1181 | + * Local helper utilities
|
|
| 1182 | + */
|
|
| 1183 | + |
|
| 1184 | +static int enlargeTables(Capability *cap, CapIOManager *iomgr)
|
|
| 1185 | +{
|
|
| 1186 | + int oldcapacity = capacityClosureTable(&iomgr->aiop_table);
|
|
| 1187 | + int newcapacity = (oldcapacity == 0) ? 1 : (oldcapacity * 2);
|
|
| 1188 | + |
|
| 1189 | + int fail = enlargeClosureTable(cap, &iomgr->aiop_table, newcapacity);
|
|
| 1190 | + if (RTS_UNLIKELY(fail)) return fail;
|
|
| 1191 | + return 0;
|
|
| 1192 | +}
|
|
| 1193 | + |
|
| 1194 | + |
|
| 1195 | +/*
|
|
| 1196 | + */
|
|
| 1197 | +static void enqueueOverflowQueue(Capability *cap, CapIOManager *iomgr,
|
|
| 1198 | + StgTSO *tso, struct io_uring_sqe *sqe)
|
|
| 1199 | +{
|
|
| 1200 | + /* Append the TSO to the tail of the overflow queue of TSOs. */
|
|
| 1201 | + ASSERT(tso->_link == END_TSO_QUEUE);
|
|
| 1202 | + if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
|
|
| 1203 | + iomgr->overflow_tso_q_hd = tso;
|
|
| 1204 | + } else {
|
|
| 1205 | + setTSOLink(cap, iomgr->overflow_tso_q_tl, tso);
|
|
| 1206 | + }
|
|
| 1207 | + iomgr->overflow_tso_q_tl = tso;
|
|
| 1208 | + |
|
| 1209 | + /* And append the SQE to the tail of the overflow queue of SQEs. */
|
|
| 1210 | + struct overflow_sqe_q_t *entry;
|
|
| 1211 | + entry = stgMallocBytes(sizeof(struct overflow_sqe_q_t), "uring iomgr");
|
|
| 1212 | + *entry = (struct overflow_sqe_q_t) {
|
|
| 1213 | + .sqe = sqe,
|
|
| 1214 | + .next = NULL,
|
|
| 1215 | +#if defined(DEBUG)
|
|
| 1216 | + .tid = tso->id
|
|
| 1217 | +#endif
|
|
| 1218 | + };
|
|
| 1219 | + if (iomgr->overflow_sqe_q_hd == NULL) {
|
|
| 1220 | + iomgr->overflow_sqe_q_hd = entry;
|
|
| 1221 | + } else {
|
|
| 1222 | + iomgr->overflow_sqe_q_tl->next = entry;
|
|
| 1223 | + }
|
|
| 1224 | + iomgr->overflow_sqe_q_tl = entry;
|
|
| 1225 | +}
|
|
| 1226 | + |
|
| 1227 | + |
|
| 1228 | +static void dequeueOverflowQueue(CapIOManager *iomgr,
|
|
| 1229 | + StgTSO **ptso, struct io_uring_sqe **psqe)
|
|
| 1230 | +{
|
|
| 1231 | + /* Remove the TSO and SQE from the head of their respective queues */
|
|
| 1232 | + StgTSO *tso = iomgr->overflow_tso_q_hd;
|
|
| 1233 | + struct overflow_sqe_q_t *entry = iomgr->overflow_sqe_q_hd;
|
|
| 1234 | + |
|
| 1235 | + if (tso == END_TSO_QUEUE) {
|
|
| 1236 | + //TODO: decide if we need this or if we should assume the queue is
|
|
| 1237 | + // non-empty
|
|
| 1238 | + ASSERT(entry == NULL);
|
|
| 1239 | + *ptso = END_TSO_QUEUE;
|
|
| 1240 | + *psqe = NULL;
|
|
| 1241 | + } else {
|
|
| 1242 | + iomgr->overflow_tso_q_hd = tso->_link;
|
|
| 1243 | + RELAXED_STORE(&tso->_link, END_TSO_QUEUE);
|
|
| 1244 | + if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
|
|
| 1245 | + iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
|
|
| 1246 | + }
|
|
| 1247 | + |
|
| 1248 | + iomgr->overflow_sqe_q_hd = entry->next;
|
|
| 1249 | + if (iomgr->overflow_sqe_q_hd == NULL) {
|
|
| 1250 | + iomgr->overflow_sqe_q_tl = NULL;
|
|
| 1251 | + }
|
|
| 1252 | + |
|
| 1253 | + *ptso = tso;
|
|
| 1254 | + *psqe = entry->sqe;
|
|
| 1255 | + }
|
|
| 1256 | +}
|
|
| 1257 | + |
|
| 1258 | +#endif /* IOMGR_ENABLED_URING */
|
|
| 1259 | + |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2021-2023
|
|
| 4 | + *
|
|
| 5 | + * An I/O manager based on the Linux io_uring API.
|
|
| 6 | + *
|
|
| 7 | + * Prototypes for functions in URing.c
|
|
| 8 | + *
|
|
| 9 | + * -------------------------------------------------------------------------*/
|
|
| 10 | + |
|
| 11 | +#pragma once
|
|
| 12 | + |
|
| 13 | +#include "IOManager.h"
|
|
| 14 | + |
|
| 15 | +#include "BeginPrivate.h"
|
|
| 16 | + |
|
| 17 | +#if defined(IOMGR_ENABLED_URING)
|
|
| 18 | + |
|
| 19 | +void initCapabilityIOManagerURing(Capability *cap, CapIOManager *iomgr);
|
|
| 20 | +void initCapabilityIOManagerAfterForkURing(Capability *cap, CapIOManager *iomgr);
|
|
| 21 | + |
|
| 22 | +/* Synchronous I/O and timer operations */
|
|
| 23 | +int syncIOWaitReadyURing(Capability *cap, StgTSO *tso,
|
|
| 24 | + IOReadOrWrite rw, int fd);
|
|
| 25 | + |
|
| 26 | +int syncIOReadWriteURing(Capability *cap, StgTSO *tso,
|
|
| 27 | + IOReadOrWrite rw, int fd,
|
|
| 28 | + StgClosure *live, void *buf,
|
|
| 29 | + size_t len, off_t off);
|
|
| 30 | + |
|
| 31 | +void syncIOCancelURing(Capability *cap, StgTSO *tso);
|
|
| 32 | + |
|
| 33 | +/* Asynchronous operations */
|
|
| 34 | +int asyncIOWaitReadyURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 35 | + IOReadOrWrite rw, int fd);
|
|
| 36 | + |
|
| 37 | +int asyncIOReadWriteURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
|
|
| 38 | + IOReadOrWrite rw, int fd,
|
|
| 39 | + StgClosure *live, void *buf,
|
|
| 40 | + size_t len, off_t off);
|
|
| 41 | + |
|
| 42 | +void asyncIOCancelURing(Capability *cap, StgAsyncIOOp *aiop);
|
|
| 43 | + |
|
| 44 | +/* Scheduler operations */
|
|
| 45 | +bool anyPendingTimeoutsOrIOURing(CapIOManager *iomgr);
|
|
| 46 | +void pollCompletedTimeoutsOrIOURing(Capability *cap);
|
|
| 47 | +void awaitCompletedTimeoutsOrIOURing(Capability *cap);
|
|
| 48 | + |
|
| 49 | +#endif /* IOMGR_ENABLED_URING */
|
|
| 50 | + |
|
| 51 | +#include "EndPrivate.h"
|
|
| 52 | + |
| 1 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
| 2 | +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
|
| 3 | +<svg width="116cm" height="39cm" viewBox="-281 18 2304 768" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
| 4 | + <g>
|
|
| 5 | + <rect style="fill: #ffffff" x="-200" y="20" width="158.525" height="70"/>
|
|
| 6 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-200" y="20" width="158.525" height="70"/>
|
|
| 7 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120.738" y="42.8812">
|
|
| 8 | + <tspan x="-120.738" y="42.8812">Primop: async</tspan>
|
|
| 9 | + <tspan x="-120.738" y="58.8812">non-blocking I/O</tspan>
|
|
| 10 | + <tspan x="-120.738" y="74.8812">submission</tspan>
|
|
| 11 | + </text>
|
|
| 12 | + </g>
|
|
| 13 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120.738" y="55">
|
|
| 14 | + <tspan x="-120.738" y="55"></tspan>
|
|
| 15 | + </text>
|
|
| 16 | + <g>
|
|
| 17 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120.738" y1="90" x2="-120.224" y2="112.267"/>
|
|
| 18 | + <polygon style="fill: #000000" points="-120.052,119.765 -125.281,109.882 -120.224,112.267 -115.283,109.652 "/>
|
|
| 19 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120.052,119.765 -125.281,109.882 -120.224,112.267 -115.283,109.652 "/>
|
|
| 20 | + </g>
|
|
| 21 | + <g>
|
|
| 22 | + <rect style="fill: #ffffff" x="-200" y="122" width="160" height="54"/>
|
|
| 23 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-200" y="122" width="160" height="54"/>
|
|
| 24 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="144.881">
|
|
| 25 | + <tspan x="-120" y="144.881">Allocate AIOP</tspan>
|
|
| 26 | + <tspan x="-120" y="160.881">Allocate table index</tspan>
|
|
| 27 | + </text>
|
|
| 28 | + </g>
|
|
| 29 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="-280" y1="105" x2="180" y2="105"/>
|
|
| 30 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="98.6222">
|
|
| 31 | + <tspan x="-280" y="98.6222">RTS CMM</tspan>
|
|
| 32 | + </text>
|
|
| 33 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="121.539">
|
|
| 34 | + <tspan x="-280" y="121.539">RTS C</tspan>
|
|
| 35 | + </text>
|
|
| 36 | + <g>
|
|
| 37 | + <rect style="fill: #ffffff" x="0" y="25" width="160" height="60"/>
|
|
| 38 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="0" y="25" width="160" height="60"/>
|
|
| 39 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="58.8813">
|
|
| 40 | + <tspan x="80" y="58.8813">Primop: GC and retry</tspan>
|
|
| 41 | + </text>
|
|
| 42 | + </g>
|
|
| 43 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="55">
|
|
| 44 | + <tspan x="80" y="55"></tspan>
|
|
| 45 | + </text>
|
|
| 46 | + <g>
|
|
| 47 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="0" y1="55" x2="-31.7389" y2="55"/>
|
|
| 48 | + <polygon style="fill: #000000" points="-39.2389,55 -29.2389,50 -31.7389,55 -29.2389,60 "/>
|
|
| 49 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-39.2389,55 -29.2389,50 -31.7389,55 -29.2389,60 "/>
|
|
| 50 | + </g>
|
|
| 51 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="149">
|
|
| 52 | + <tspan x="-120" y="149"></tspan>
|
|
| 53 | + </text>
|
|
| 54 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="156.981">
|
|
| 55 | + <tspan x="-20" y="156.981">mem alloc failure?</tspan>
|
|
| 56 | + </text>
|
|
| 57 | + <g>
|
|
| 58 | + <polygon style="fill: #ffffff" points="-120,262 -40,322.81 -120,383.619 -200,322.81 "/>
|
|
| 59 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,262 -40,322.81 -120,383.619 -200,322.81 "/>
|
|
| 60 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="310.691">
|
|
| 61 | + <tspan x="-120" y="310.691">inflight</tspan>
|
|
| 62 | + <tspan x="-120" y="326.691">within</tspan>
|
|
| 63 | + <tspan x="-120" y="342.691">limit?</tspan>
|
|
| 64 | + </text>
|
|
| 65 | + </g>
|
|
| 66 | + <g>
|
|
| 67 | + <rect style="fill: #ffffff" x="-200" y="402" width="160" height="60"/>
|
|
| 68 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-200" y="402" width="160" height="60"/>
|
|
| 69 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="427.881">
|
|
| 70 | + <tspan x="-120" y="427.881">Allocate SQE on ring</tspan>
|
|
| 71 | + <tspan x="-120" y="443.881">Inc prep counter</tspan>
|
|
| 72 | + </text>
|
|
| 73 | + </g>
|
|
| 74 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-80" y="386.431">
|
|
| 75 | + <tspan x="-80" y="386.431">Yes</tspan>
|
|
| 76 | + </text>
|
|
| 77 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="346.431">
|
|
| 78 | + <tspan x="-20" y="346.431">No</tspan>
|
|
| 79 | + </text>
|
|
| 80 | + <g>
|
|
| 81 | + <rect style="fill: #ffffff" x="-12.825" y="395" width="185.65" height="70"/>
|
|
| 82 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-12.825" y="395" width="185.65" height="70"/>
|
|
| 83 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="417.881">
|
|
| 84 | + <tspan x="80" y="417.881">Allocate SQE on heap</tspan>
|
|
| 85 | + <tspan x="80" y="433.881">Inc overflow counter</tspan>
|
|
| 86 | + <tspan x="80" y="449.881">TSO & SQE on overflow Q</tspan>
|
|
| 87 | + </text>
|
|
| 88 | + </g>
|
|
| 89 | + <g>
|
|
| 90 | + <rect style="fill: #ffffff" x="-201.625" y="202" width="163.25" height="40"/>
|
|
| 91 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-201.625" y="202" width="163.25" height="40"/>
|
|
| 92 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="225.881">
|
|
| 93 | + <tspan x="-120" y="225.881">Inc submitted counter</tspan>
|
|
| 94 | + </text>
|
|
| 95 | + </g>
|
|
| 96 | + <g>
|
|
| 97 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="384.619" x2="-120" y2="392.264"/>
|
|
| 98 | + <polygon style="fill: #000000" points="-120,399.764 -125,389.764 -120,392.264 -115,389.764 "/>
|
|
| 99 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,399.764 -125,389.764 -120,392.264 -115,389.764 "/>
|
|
| 100 | + </g>
|
|
| 101 | + <g>
|
|
| 102 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="176" x2="-120" y2="192.264"/>
|
|
| 103 | + <polygon style="fill: #000000" points="-120,199.764 -125,189.764 -120,192.264 -115,189.764 "/>
|
|
| 104 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,199.764 -125,189.764 -120,192.264 -115,189.764 "/>
|
|
| 105 | + </g>
|
|
| 106 | + <g>
|
|
| 107 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="242" x2="-120" y2="252.264"/>
|
|
| 108 | + <polygon style="fill: #000000" points="-120,259.764 -125,249.764 -120,252.264 -115,249.764 "/>
|
|
| 109 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,259.764 -125,249.764 -120,252.264 -115,249.764 "/>
|
|
| 110 | + </g>
|
|
| 111 | + <g>
|
|
| 112 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-40,149 80,125 80,94.7361 "/>
|
|
| 113 | + <polygon style="fill: #000000" points="80,87.2361 85,97.2361 80,94.7361 75,97.2361 "/>
|
|
| 114 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="80,87.2361 85,97.2361 80,94.7361 75,97.2361 "/>
|
|
| 115 | + </g>
|
|
| 116 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="430">
|
|
| 117 | + <tspan x="80" y="430"></tspan>
|
|
| 118 | + </text>
|
|
| 119 | + <g>
|
|
| 120 | + <rect style="fill: #ffffff" x="-100" y="482" width="160" height="40"/>
|
|
| 121 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-100" y="482" width="160" height="40"/>
|
|
| 122 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="505.881">
|
|
| 123 | + <tspan x="-20" y="505.881">Fill in SQE</tspan>
|
|
| 124 | + </text>
|
|
| 125 | + </g>
|
|
| 126 | + <g>
|
|
| 127 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="462" x2="-106.884" y2="475.116"/>
|
|
| 128 | + <polygon style="fill: #000000" points="-101.581,480.419 -112.188,476.883 -106.884,475.116 -105.117,469.812 "/>
|
|
| 129 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-101.581,480.419 -112.188,476.883 -106.884,475.116 -105.117,469.812 "/>
|
|
| 130 | + </g>
|
|
| 131 | + <g>
|
|
| 132 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="80" y1="465" x2="67.4183" y2="475.694"/>
|
|
| 133 | + <polygon style="fill: #000000" points="61.7037,480.552 66.0849,470.266 67.4183,475.694 72.5614,477.885 "/>
|
|
| 134 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="61.7037,480.552 66.0849,470.266 67.4183,475.694 72.5614,477.885 "/>
|
|
| 135 | + </g>
|
|
| 136 | + <g>
|
|
| 137 | + <rect style="fill: #ffffff" x="-195.75" y="677" width="157.15" height="70"/>
|
|
| 138 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-195.75" y="677" width="157.15" height="70"/>
|
|
| 139 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-117.175" y="699.881">
|
|
| 140 | + <tspan x="-117.175" y="699.881">Primop:</tspan>
|
|
| 141 | + <tspan x="-117.175" y="715.881">result is AIOP</tspan>
|
|
| 142 | + <tspan x="-117.175" y="731.881">control to caller</tspan>
|
|
| 143 | + </text>
|
|
| 144 | + </g>
|
|
| 145 | + <g>
|
|
| 146 | + <rect style="fill: #ffffff" x="0" y="677" width="160" height="70"/>
|
|
| 147 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="0" y="677" width="160" height="70"/>
|
|
| 148 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="699.881">
|
|
| 149 | + <tspan x="80" y="699.881">Primop:</tspan>
|
|
| 150 | + <tspan x="80" y="715.881">result is AIOP</tspan>
|
|
| 151 | + <tspan x="80" y="731.881">control to scheduler</tspan>
|
|
| 152 | + </text>
|
|
| 153 | + </g>
|
|
| 154 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="-280" y1="662" x2="180" y2="662"/>
|
|
| 155 | + <g>
|
|
| 156 | + <polygon style="fill: #ffffff" points="-20,542 120,592 -20,642 -160,592 "/>
|
|
| 157 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-20,542 120,592 -20,642 -160,592 "/>
|
|
| 158 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="587.881">
|
|
| 159 | + <tspan x="-20" y="587.881">inflight within limit</tspan>
|
|
| 160 | + <tspan x="-20" y="603.881">And SQ ring not full</tspan>
|
|
| 161 | + </text>
|
|
| 162 | + </g>
|
|
| 163 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="682">
|
|
| 164 | + <tspan x="-280" y="682">RTS CMM</tspan>
|
|
| 165 | + </text>
|
|
| 166 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="655.622">
|
|
| 167 | + <tspan x="-280" y="655.622">RTS C</tspan>
|
|
| 168 | + </text>
|
|
| 169 | + <g>
|
|
| 170 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-20" y1="522.977" x2="-20" y2="532.264"/>
|
|
| 171 | + <polygon style="fill: #000000" points="-20,539.764 -25,529.764 -20,532.264 -15,529.764 "/>
|
|
| 172 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-20,539.764 -25,529.764 -20,532.264 -15,529.764 "/>
|
|
| 173 | + </g>
|
|
| 174 | + <g>
|
|
| 175 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-55" y1="629.5" x2="-73.6614" y2="668.229"/>
|
|
| 176 | + <polygon style="fill: #000000" points="-76.917,674.986 -77.0805,663.806 -73.6614,668.229 -68.0718,668.147 "/>
|
|
| 177 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-76.917,674.986 -77.0805,663.806 -73.6614,668.229 -68.0718,668.147 "/>
|
|
| 178 | + </g>
|
|
| 179 | + <g>
|
|
| 180 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="15" y1="629.5" x2="35.4655" y2="668.384"/>
|
|
| 181 | + <polygon style="fill: #000000" points="38.9586,675.021 29.8765,668.501 35.4655,668.384 38.7257,663.843 "/>
|
|
| 182 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="38.9586,675.021 29.8765,668.501 35.4655,668.384 38.7257,663.843 "/>
|
|
| 183 | + </g>
|
|
| 184 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-100" y="646.431">
|
|
| 185 | + <tspan x="-100" y="646.431">Yes</tspan>
|
|
| 186 | + </text>
|
|
| 187 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="40" y="646.431">
|
|
| 188 | + <tspan x="40" y="646.431">No</tspan>
|
|
| 189 | + </text>
|
|
| 190 | + <g>
|
|
| 191 | + <rect style="fill: #ffffff" x="1019.34" y="25" width="163.85" height="70"/>
|
|
| 192 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1019.34" y="25" width="163.85" height="70"/>
|
|
| 193 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1101.26" y="47.8812">
|
|
| 194 | + <tspan x="1101.26" y="47.8812">Primop: async</tspan>
|
|
| 195 | + <tspan x="1101.26" y="63.8812">blocking I/O</tspan>
|
|
| 196 | + <tspan x="1101.26" y="79.8812">submission</tspan>
|
|
| 197 | + </text>
|
|
| 198 | + </g>
|
|
| 199 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1101.26" y="60">
|
|
| 200 | + <tspan x="1101.26" y="60"></tspan>
|
|
| 201 | + </text>
|
|
| 202 | + <g>
|
|
| 203 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1101.26" y1="95" x2="1101.76" y2="115.267"/>
|
|
| 204 | + <polygon style="fill: #000000" points="1101.95,122.765 1096.7,112.89 1101.76,115.267 1106.7,112.645 "/>
|
|
| 205 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1101.95,122.765 1096.7,112.89 1101.76,115.267 1106.7,112.645 "/>
|
|
| 206 | + </g>
|
|
| 207 | + <g>
|
|
| 208 | + <rect style="fill: #ffffff" x="1022" y="125" width="160" height="60"/>
|
|
| 209 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="125" width="160" height="60"/>
|
|
| 210 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="150.881">
|
|
| 211 | + <tspan x="1102" y="150.881">Allocate AIOP</tspan>
|
|
| 212 | + <tspan x="1102" y="166.881">Allocate table index</tspan>
|
|
| 213 | + </text>
|
|
| 214 | + </g>
|
|
| 215 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="942" y1="110" x2="1402" y2="110"/>
|
|
| 216 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="103.622">
|
|
| 217 | + <tspan x="942" y="103.622">RTS CMM</tspan>
|
|
| 218 | + </text>
|
|
| 219 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="126.539">
|
|
| 220 | + <tspan x="942" y="126.539">RTS C</tspan>
|
|
| 221 | + </text>
|
|
| 222 | + <g>
|
|
| 223 | + <rect style="fill: #ffffff" x="1222" y="30" width="160" height="60"/>
|
|
| 224 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1222" y="30" width="160" height="60"/>
|
|
| 225 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1302" y="63.8813">
|
|
| 226 | + <tspan x="1302" y="63.8813">Primop: GC and retry</tspan>
|
|
| 227 | + </text>
|
|
| 228 | + </g>
|
|
| 229 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1302" y="60">
|
|
| 230 | + <tspan x="1302" y="60"></tspan>
|
|
| 231 | + </text>
|
|
| 232 | + <g>
|
|
| 233 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1222" y1="60" x2="1192.92" y2="60"/>
|
|
| 234 | + <polygon style="fill: #000000" points="1185.42,60 1195.42,55 1192.92,60 1195.42,65 "/>
|
|
| 235 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1185.42,60 1195.42,55 1192.92,60 1195.42,65 "/>
|
|
| 236 | + </g>
|
|
| 237 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="155">
|
|
| 238 | + <tspan x="1102" y="155"></tspan>
|
|
| 239 | + </text>
|
|
| 240 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1222" y="161.981">
|
|
| 241 | + <tspan x="1222" y="161.981">mem alloc failure?</tspan>
|
|
| 242 | + </text>
|
|
| 243 | + <g>
|
|
| 244 | + <polygon style="fill: #ffffff" points="1102,285 1182,345.81 1102,406.619 1022,345.81 "/>
|
|
| 245 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,285 1182,345.81 1102,406.619 1022,345.81 "/>
|
|
| 246 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="333.691">
|
|
| 247 | + <tspan x="1102" y="333.691">inflight</tspan>
|
|
| 248 | + <tspan x="1102" y="349.691">within</tspan>
|
|
| 249 | + <tspan x="1102" y="365.691">limit?</tspan>
|
|
| 250 | + </text>
|
|
| 251 | + </g>
|
|
| 252 | + <g>
|
|
| 253 | + <rect style="fill: #ffffff" x="1022" y="440" width="160" height="60"/>
|
|
| 254 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="440" width="160" height="60"/>
|
|
| 255 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="465.881">
|
|
| 256 | + <tspan x="1102" y="465.881">Allocate SQE on ring</tspan>
|
|
| 257 | + <tspan x="1102" y="481.881">Inc ring prep counter</tspan>
|
|
| 258 | + </text>
|
|
| 259 | + </g>
|
|
| 260 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1042" y="409.431">
|
|
| 261 | + <tspan x="1042" y="409.431">Yes</tspan>
|
|
| 262 | + </text>
|
|
| 263 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1142" y="409.431">
|
|
| 264 | + <tspan x="1142" y="409.431">No</tspan>
|
|
| 265 | + </text>
|
|
| 266 | + <g>
|
|
| 267 | + <rect style="fill: #ffffff" x="1020.37" y="205" width="163.25" height="40"/>
|
|
| 268 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1020.37" y="205" width="163.25" height="40"/>
|
|
| 269 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="228.881">
|
|
| 270 | + <tspan x="1102" y="228.881">Inc submitted counter</tspan>
|
|
| 271 | + </text>
|
|
| 272 | + </g>
|
|
| 273 | + <g>
|
|
| 274 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="406.62" x2="1102" y2="430.264"/>
|
|
| 275 | + <polygon style="fill: #000000" points="1102,437.764 1097,427.764 1102,430.264 1107,427.764 "/>
|
|
| 276 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,437.764 1097,427.764 1102,430.264 1107,427.764 "/>
|
|
| 277 | + </g>
|
|
| 278 | + <g>
|
|
| 279 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="185" x2="1102" y2="195.264"/>
|
|
| 280 | + <polygon style="fill: #000000" points="1102,202.764 1097,192.764 1102,195.264 1107,192.764 "/>
|
|
| 281 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,202.764 1097,192.764 1102,195.264 1107,192.764 "/>
|
|
| 282 | + </g>
|
|
| 283 | + <g>
|
|
| 284 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="245" x2="1102" y2="275.264"/>
|
|
| 285 | + <polygon style="fill: #000000" points="1102,282.764 1097,272.764 1102,275.264 1107,272.764 "/>
|
|
| 286 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,282.764 1097,272.764 1102,275.264 1107,272.764 "/>
|
|
| 287 | + </g>
|
|
| 288 | + <g>
|
|
| 289 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1182,155 1302,130 1302,99.7361 "/>
|
|
| 290 | + <polygon style="fill: #000000" points="1302,92.2361 1307,102.236 1302,99.7361 1297,102.236 "/>
|
|
| 291 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1302,92.2361 1307,102.236 1302,99.7361 1297,102.236 "/>
|
|
| 292 | + </g>
|
|
| 293 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1202" y="455">
|
|
| 294 | + <tspan x="1202" y="455"></tspan>
|
|
| 295 | + </text>
|
|
| 296 | + <g>
|
|
| 297 | + <rect style="fill: #ffffff" x="1022" y="525" width="160" height="40"/>
|
|
| 298 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="525" width="160" height="40"/>
|
|
| 299 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="548.881">
|
|
| 300 | + <tspan x="1102" y="548.881">Fill in SQE</tspan>
|
|
| 301 | + </text>
|
|
| 302 | + </g>
|
|
| 303 | + <g>
|
|
| 304 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="500" x2="1102" y2="515.264"/>
|
|
| 305 | + <polygon style="fill: #000000" points="1102,522.764 1097,512.764 1102,515.264 1107,512.764 "/>
|
|
| 306 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,522.764 1097,512.764 1102,515.264 1107,512.764 "/>
|
|
| 307 | + </g>
|
|
| 308 | + <g>
|
|
| 309 | + <rect style="fill: #ffffff" x="1022" y="716" width="160" height="70"/>
|
|
| 310 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="716" width="160" height="70"/>
|
|
| 311 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="738.881">
|
|
| 312 | + <tspan x="1102" y="738.881">Primop:</tspan>
|
|
| 313 | + <tspan x="1102" y="754.881">result is AIOP</tspan>
|
|
| 314 | + <tspan x="1102" y="770.881">control to caller</tspan>
|
|
| 315 | + </text>
|
|
| 316 | + </g>
|
|
| 317 | + <g>
|
|
| 318 | + <rect style="fill: #ffffff" x="1222" y="717" width="160" height="60"/>
|
|
| 319 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1222" y="717" width="160" height="60"/>
|
|
| 320 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1302" y="742.881">
|
|
| 321 | + <tspan x="1302" y="742.881">Primop:</tspan>
|
|
| 322 | + <tspan x="1302" y="758.881">throw exception</tspan>
|
|
| 323 | + </text>
|
|
| 324 | + </g>
|
|
| 325 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="942" y1="697" x2="1402" y2="697"/>
|
|
| 326 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="717">
|
|
| 327 | + <tspan x="942" y="717">RTS CMM</tspan>
|
|
| 328 | + </text>
|
|
| 329 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="690.622">
|
|
| 330 | + <tspan x="942" y="690.622">RTS C</tspan>
|
|
| 331 | + </text>
|
|
| 332 | + <g>
|
|
| 333 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102.53" y1="565.994" x2="1102.86" y2="579.104"/>
|
|
| 334 | + <polygon style="fill: #000000" points="1103.05,586.601 1097.8,576.73 1102.86,579.104 1107.79,576.479 "/>
|
|
| 335 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1103.05,586.601 1097.8,576.73 1102.86,579.104 1107.79,576.479 "/>
|
|
| 336 | + </g>
|
|
| 337 | + <g>
|
|
| 338 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1182,345.81 1182,345 1302,345 1302,707.264 "/>
|
|
| 339 | + <polygon style="fill: #000000" points="1302,714.764 1297,704.764 1302,707.264 1307,704.764 "/>
|
|
| 340 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1302,714.764 1297,704.764 1302,707.264 1307,704.764 "/>
|
|
| 341 | + </g>
|
|
| 342 | + <g>
|
|
| 343 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-40,322.81 -40,322 80,322 80,385.264 "/>
|
|
| 344 | + <polygon style="fill: #000000" points="80,392.764 75,382.764 80,385.264 85,382.764 "/>
|
|
| 345 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="80,392.764 75,382.764 80,385.264 85,382.764 "/>
|
|
| 346 | + </g>
|
|
| 347 | + <g>
|
|
| 348 | + <rect style="fill: #ffffff" x="403" y="22" width="158.525" height="70"/>
|
|
| 349 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="403" y="22" width="158.525" height="70"/>
|
|
| 350 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="482.262" y="44.8813">
|
|
| 351 | + <tspan x="482.262" y="44.8813">Primop: sync</tspan>
|
|
| 352 | + <tspan x="482.262" y="60.8812">non-blocking I/O</tspan>
|
|
| 353 | + <tspan x="482.262" y="76.8812">submission</tspan>
|
|
| 354 | + </text>
|
|
| 355 | + </g>
|
|
| 356 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="482.262" y="57">
|
|
| 357 | + <tspan x="482.262" y="57"></tspan>
|
|
| 358 | + </text>
|
|
| 359 | + <g>
|
|
| 360 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="482.262" y1="92" x2="482.795" y2="117.266"/>
|
|
| 361 | + <polygon style="fill: #000000" points="482.953,124.764 477.743,114.872 482.795,117.266 487.741,114.661 "/>
|
|
| 362 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="482.953,124.764 477.743,114.872 482.795,117.266 487.741,114.661 "/>
|
|
| 363 | + </g>
|
|
| 364 | + <g>
|
|
| 365 | + <rect style="fill: #ffffff" x="403" y="127" width="160" height="60"/>
|
|
| 366 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="403" y="127" width="160" height="60"/>
|
|
| 367 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="152.881">
|
|
| 368 | + <tspan x="483" y="152.881">Allocate AIOP</tspan>
|
|
| 369 | + <tspan x="483" y="168.881">Allocate table index</tspan>
|
|
| 370 | + </text>
|
|
| 371 | + </g>
|
|
| 372 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="323" y1="107" x2="783" y2="107"/>
|
|
| 373 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="100.622">
|
|
| 374 | + <tspan x="323" y="100.622">RTS CMM</tspan>
|
|
| 375 | + </text>
|
|
| 376 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="123.539">
|
|
| 377 | + <tspan x="323" y="123.539">RTS C</tspan>
|
|
| 378 | + </text>
|
|
| 379 | + <g>
|
|
| 380 | + <rect style="fill: #ffffff" x="603" y="27" width="160" height="60"/>
|
|
| 381 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="603" y="27" width="160" height="60"/>
|
|
| 382 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="60.8812">
|
|
| 383 | + <tspan x="683" y="60.8812">Primop: GC and retry</tspan>
|
|
| 384 | + </text>
|
|
| 385 | + </g>
|
|
| 386 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="57">
|
|
| 387 | + <tspan x="683" y="57"></tspan>
|
|
| 388 | + </text>
|
|
| 389 | + <g>
|
|
| 390 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="603" y1="57" x2="571.262" y2="57"/>
|
|
| 391 | + <polygon style="fill: #000000" points="563.762,57 573.762,52 571.262,57 573.762,62 "/>
|
|
| 392 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="563.762,57 573.762,52 571.262,57 573.762,62 "/>
|
|
| 393 | + </g>
|
|
| 394 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="157">
|
|
| 395 | + <tspan x="483" y="157"></tspan>
|
|
| 396 | + </text>
|
|
| 397 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="163.981">
|
|
| 398 | + <tspan x="583" y="163.981">mem alloc failure?</tspan>
|
|
| 399 | + </text>
|
|
| 400 | + <g>
|
|
| 401 | + <polygon style="fill: #ffffff" points="483,262 563,322.81 483,383.619 403,322.81 "/>
|
|
| 402 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,262 563,322.81 483,383.619 403,322.81 "/>
|
|
| 403 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="310.691">
|
|
| 404 | + <tspan x="483" y="310.691">inflight</tspan>
|
|
| 405 | + <tspan x="483" y="326.691">within</tspan>
|
|
| 406 | + <tspan x="483" y="342.691">limit?</tspan>
|
|
| 407 | + </text>
|
|
| 408 | + </g>
|
|
| 409 | + <g>
|
|
| 410 | + <rect style="fill: #ffffff" x="403" y="402" width="160" height="60"/>
|
|
| 411 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="403" y="402" width="160" height="60"/>
|
|
| 412 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="427.881">
|
|
| 413 | + <tspan x="483" y="427.881">Allocate SQE on ring</tspan>
|
|
| 414 | + <tspan x="483" y="443.881">Inc ring prep counter</tspan>
|
|
| 415 | + </text>
|
|
| 416 | + </g>
|
|
| 417 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="523" y="386.431">
|
|
| 418 | + <tspan x="523" y="386.431">Yes</tspan>
|
|
| 419 | + </text>
|
|
| 420 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="346.431">
|
|
| 421 | + <tspan x="583" y="346.431">No</tspan>
|
|
| 422 | + </text>
|
|
| 423 | + <g>
|
|
| 424 | + <rect style="fill: #ffffff" x="603" y="402" width="160" height="60"/>
|
|
| 425 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="603" y="402" width="160" height="60"/>
|
|
| 426 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="427.881">
|
|
| 427 | + <tspan x="683" y="427.881">Allocate SQE on heap</tspan>
|
|
| 428 | + <tspan x="683" y="443.881">TSO on overflow Q</tspan>
|
|
| 429 | + </text>
|
|
| 430 | + </g>
|
|
| 431 | + <g>
|
|
| 432 | + <rect style="fill: #ffffff" x="401.375" y="207" width="163.25" height="40"/>
|
|
| 433 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="401.375" y="207" width="163.25" height="40"/>
|
|
| 434 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="230.881">
|
|
| 435 | + <tspan x="483" y="230.881">Inc submitted counter</tspan>
|
|
| 436 | + </text>
|
|
| 437 | + </g>
|
|
| 438 | + <g>
|
|
| 439 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="384.619" x2="483" y2="392.264"/>
|
|
| 440 | + <polygon style="fill: #000000" points="483,399.764 478,389.764 483,392.264 488,389.764 "/>
|
|
| 441 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,399.764 478,389.764 483,392.264 488,389.764 "/>
|
|
| 442 | + </g>
|
|
| 443 | + <g>
|
|
| 444 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="187" x2="483" y2="197.264"/>
|
|
| 445 | + <polygon style="fill: #000000" points="483,204.764 478,194.764 483,197.264 488,194.764 "/>
|
|
| 446 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,204.764 478,194.764 483,197.264 488,194.764 "/>
|
|
| 447 | + </g>
|
|
| 448 | + <g>
|
|
| 449 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="247" x2="483" y2="252.264"/>
|
|
| 450 | + <polygon style="fill: #000000" points="483,259.764 478,249.764 483,252.264 488,249.764 "/>
|
|
| 451 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,259.764 478,249.764 483,252.264 488,249.764 "/>
|
|
| 452 | + </g>
|
|
| 453 | + <g>
|
|
| 454 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="563,157 683,127 683,96.7361 "/>
|
|
| 455 | + <polygon style="fill: #000000" points="683,89.2361 688,99.2361 683,96.7361 678,99.2361 "/>
|
|
| 456 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="683,89.2361 688,99.2361 683,96.7361 678,99.2361 "/>
|
|
| 457 | + </g>
|
|
| 458 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="432">
|
|
| 459 | + <tspan x="683" y="432"></tspan>
|
|
| 460 | + </text>
|
|
| 461 | + <g>
|
|
| 462 | + <rect style="fill: #ffffff" x="503" y="482" width="160" height="40"/>
|
|
| 463 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="503" y="482" width="160" height="40"/>
|
|
| 464 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="505.881">
|
|
| 465 | + <tspan x="583" y="505.881">Fill in SQE</tspan>
|
|
| 466 | + </text>
|
|
| 467 | + </g>
|
|
| 468 | + <g>
|
|
| 469 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="462" x2="496.116" y2="475.116"/>
|
|
| 470 | + <polygon style="fill: #000000" points="501.419,480.419 490.812,476.883 496.116,475.116 497.883,469.812 "/>
|
|
| 471 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="501.419,480.419 490.812,476.883 496.116,475.116 497.883,469.812 "/>
|
|
| 472 | + </g>
|
|
| 473 | + <g>
|
|
| 474 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="683" y1="462" x2="669.884" y2="475.116"/>
|
|
| 475 | + <polygon style="fill: #000000" points="664.581,480.419 668.117,469.812 669.884,475.116 675.188,476.883 "/>
|
|
| 476 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="664.581,480.419 668.117,469.812 669.884,475.116 675.188,476.883 "/>
|
|
| 477 | + </g>
|
|
| 478 | + <g>
|
|
| 479 | + <rect style="fill: #ffffff" x="503" y="562" width="160" height="60"/>
|
|
| 480 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="503" y="562" width="160" height="60"/>
|
|
| 481 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="587.881">
|
|
| 482 | + <tspan x="583" y="587.881">Primop:</tspan>
|
|
| 483 | + <tspan x="583" y="603.881">control to scheduler</tspan>
|
|
| 484 | + </text>
|
|
| 485 | + </g>
|
|
| 486 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="323" y1="542" x2="783" y2="542"/>
|
|
| 487 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="562">
|
|
| 488 | + <tspan x="323" y="562">RTS CMM</tspan>
|
|
| 489 | + </text>
|
|
| 490 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="535.622">
|
|
| 491 | + <tspan x="323" y="535.622">RTS C</tspan>
|
|
| 492 | + </text>
|
|
| 493 | + <g>
|
|
| 494 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="583" y1="522.991" x2="583" y2="552.264"/>
|
|
| 495 | + <polygon style="fill: #000000" points="583,559.764 578,549.764 583,552.264 588,549.764 "/>
|
|
| 496 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="583,559.764 578,549.764 583,552.264 588,549.764 "/>
|
|
| 497 | + </g>
|
|
| 498 | + <g>
|
|
| 499 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="563,322.81 563,322 683,322 683,392.264 "/>
|
|
| 500 | + <polygon style="fill: #000000" points="683,399.764 678,389.764 683,392.264 688,389.764 "/>
|
|
| 501 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="683,399.764 678,389.764 683,392.264 688,389.764 "/>
|
|
| 502 | + </g>
|
|
| 503 | + <g>
|
|
| 504 | + <rect style="fill: #ffffff" x="1638.85" y="38.6" width="163.85" height="70"/>
|
|
| 505 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1638.85" y="38.6" width="163.85" height="70"/>
|
|
| 506 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1720.77" y="61.4813">
|
|
| 507 | + <tspan x="1720.77" y="61.4813">Primop: sync</tspan>
|
|
| 508 | + <tspan x="1720.77" y="77.4813">blocking I/O</tspan>
|
|
| 509 | + <tspan x="1720.77" y="93.4812">submission</tspan>
|
|
| 510 | + </text>
|
|
| 511 | + </g>
|
|
| 512 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1720.77" y="73.6">
|
|
| 513 | + <tspan x="1720.77" y="73.6"></tspan>
|
|
| 514 | + </text>
|
|
| 515 | + <g>
|
|
| 516 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1720.77" y1="108.6" x2="1721.27" y2="128.867"/>
|
|
| 517 | + <polygon style="fill: #000000" points="1721.46,136.365 1716.21,126.491 1721.27,128.867 1726.21,126.245 "/>
|
|
| 518 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.46,136.365 1716.21,126.491 1721.27,128.867 1726.21,126.245 "/>
|
|
| 519 | + </g>
|
|
| 520 | + <g>
|
|
| 521 | + <rect style="fill: #ffffff" x="1641.51" y="138.6" width="160" height="60"/>
|
|
| 522 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="138.6" width="160" height="60"/>
|
|
| 523 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="164.481">
|
|
| 524 | + <tspan x="1721.51" y="164.481">Allocate AIOP</tspan>
|
|
| 525 | + <tspan x="1721.51" y="180.481">Allocate table index</tspan>
|
|
| 526 | + </text>
|
|
| 527 | + </g>
|
|
| 528 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="1561.51" y1="123.6" x2="2021.52" y2="123.6"/>
|
|
| 529 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="117.222">
|
|
| 530 | + <tspan x="1561.51" y="117.222">RTS CMM</tspan>
|
|
| 531 | + </text>
|
|
| 532 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="140.139">
|
|
| 533 | + <tspan x="1561.51" y="140.139">RTS C</tspan>
|
|
| 534 | + </text>
|
|
| 535 | + <g>
|
|
| 536 | + <rect style="fill: #ffffff" x="1841.51" y="43.6" width="160" height="60"/>
|
|
| 537 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1841.51" y="43.6" width="160" height="60"/>
|
|
| 538 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1921.51" y="77.4813">
|
|
| 539 | + <tspan x="1921.51" y="77.4813">Primop: GC and retry</tspan>
|
|
| 540 | + </text>
|
|
| 541 | + </g>
|
|
| 542 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1921.51" y="73.6">
|
|
| 543 | + <tspan x="1921.51" y="73.6"></tspan>
|
|
| 544 | + </text>
|
|
| 545 | + <g>
|
|
| 546 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1841.51" y1="73.6" x2="1812.44" y2="73.6"/>
|
|
| 547 | + <polygon style="fill: #000000" points="1804.94,73.6 1814.94,68.6 1812.44,73.6 1814.94,78.6 "/>
|
|
| 548 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1804.94,73.6 1814.94,68.6 1812.44,73.6 1814.94,78.6 "/>
|
|
| 549 | + </g>
|
|
| 550 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="168.6">
|
|
| 551 | + <tspan x="1721.51" y="168.6"></tspan>
|
|
| 552 | + </text>
|
|
| 553 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1841.51" y="175.581">
|
|
| 554 | + <tspan x="1841.51" y="175.581">mem alloc failure?</tspan>
|
|
| 555 | + </text>
|
|
| 556 | + <g>
|
|
| 557 | + <polygon style="fill: #ffffff" points="1721.51,298.6 1801.51,359.41 1721.51,420.219 1641.51,359.41 "/>
|
|
| 558 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,298.6 1801.51,359.41 1721.51,420.219 1641.51,359.41 "/>
|
|
| 559 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="347.291">
|
|
| 560 | + <tspan x="1721.51" y="347.291">inflight</tspan>
|
|
| 561 | + <tspan x="1721.51" y="363.291">within</tspan>
|
|
| 562 | + <tspan x="1721.51" y="379.291">limit?</tspan>
|
|
| 563 | + </text>
|
|
| 564 | + </g>
|
|
| 565 | + <g>
|
|
| 566 | + <rect style="fill: #ffffff" x="1641.51" y="453.6" width="160" height="60"/>
|
|
| 567 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="453.6" width="160" height="60"/>
|
|
| 568 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="479.481">
|
|
| 569 | + <tspan x="1721.51" y="479.481">Allocate SQE on ring</tspan>
|
|
| 570 | + <tspan x="1721.51" y="495.481">Inc ring prep counter</tspan>
|
|
| 571 | + </text>
|
|
| 572 | + </g>
|
|
| 573 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1661.51" y="423.031">
|
|
| 574 | + <tspan x="1661.51" y="423.031">Yes</tspan>
|
|
| 575 | + </text>
|
|
| 576 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1761.51" y="423.031">
|
|
| 577 | + <tspan x="1761.51" y="423.031">No</tspan>
|
|
| 578 | + </text>
|
|
| 579 | + <g>
|
|
| 580 | + <rect style="fill: #ffffff" x="1639.89" y="218.6" width="163.25" height="40"/>
|
|
| 581 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1639.89" y="218.6" width="163.25" height="40"/>
|
|
| 582 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="242.481">
|
|
| 583 | + <tspan x="1721.51" y="242.481">Inc submitted counter</tspan>
|
|
| 584 | + </text>
|
|
| 585 | + </g>
|
|
| 586 | + <g>
|
|
| 587 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="420.22" x2="1721.51" y2="443.864"/>
|
|
| 588 | + <polygon style="fill: #000000" points="1721.51,451.364 1716.51,441.364 1721.51,443.864 1726.51,441.364 "/>
|
|
| 589 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,451.364 1716.51,441.364 1721.51,443.864 1726.51,441.364 "/>
|
|
| 590 | + </g>
|
|
| 591 | + <g>
|
|
| 592 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="198.6" x2="1721.51" y2="208.864"/>
|
|
| 593 | + <polygon style="fill: #000000" points="1721.51,216.364 1716.51,206.364 1721.51,208.864 1726.51,206.364 "/>
|
|
| 594 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,216.364 1716.51,206.364 1721.51,208.864 1726.51,206.364 "/>
|
|
| 595 | + </g>
|
|
| 596 | + <g>
|
|
| 597 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="258.6" x2="1721.51" y2="288.864"/>
|
|
| 598 | + <polygon style="fill: #000000" points="1721.51,296.364 1716.51,286.364 1721.51,288.864 1726.51,286.364 "/>
|
|
| 599 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,296.364 1716.51,286.364 1721.51,288.864 1726.51,286.364 "/>
|
|
| 600 | + </g>
|
|
| 601 | + <g>
|
|
| 602 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1801.51,168.6 1921.51,143.6 1921.51,113.336 "/>
|
|
| 603 | + <polygon style="fill: #000000" points="1921.51,105.836 1926.51,115.836 1921.51,113.336 1916.51,115.836 "/>
|
|
| 604 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1921.51,105.836 1926.51,115.836 1921.51,113.336 1916.51,115.836 "/>
|
|
| 605 | + </g>
|
|
| 606 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1821.51" y="468.6">
|
|
| 607 | + <tspan x="1821.51" y="468.6"></tspan>
|
|
| 608 | + </text>
|
|
| 609 | + <g>
|
|
| 610 | + <rect style="fill: #ffffff" x="1641.51" y="538.6" width="160" height="40"/>
|
|
| 611 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="538.6" width="160" height="40"/>
|
|
| 612 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="562.481">
|
|
| 613 | + <tspan x="1721.51" y="562.481">Fill in SQE</tspan>
|
|
| 614 | + </text>
|
|
| 615 | + </g>
|
|
| 616 | + <g>
|
|
| 617 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="513.6" x2="1721.51" y2="528.864"/>
|
|
| 618 | + <polygon style="fill: #000000" points="1721.51,536.364 1716.51,526.364 1721.51,528.864 1726.51,526.364 "/>
|
|
| 619 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,536.364 1716.51,526.364 1721.51,528.864 1726.51,526.364 "/>
|
|
| 620 | + </g>
|
|
| 621 | + <g>
|
|
| 622 | + <rect style="fill: #ffffff" x="1641.51" y="618.6" width="160" height="60"/>
|
|
| 623 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="618.6" width="160" height="60"/>
|
|
| 624 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="644.481">
|
|
| 625 | + <tspan x="1721.51" y="644.481">Primop:</tspan>
|
|
| 626 | + <tspan x="1721.51" y="660.481">return to scheduler</tspan>
|
|
| 627 | + </text>
|
|
| 628 | + </g>
|
|
| 629 | + <g>
|
|
| 630 | + <rect style="fill: #ffffff" x="1841.51" y="618.6" width="160" height="60"/>
|
|
| 631 | + <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1841.51" y="618.6" width="160" height="60"/>
|
|
| 632 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1921.51" y="644.481">
|
|
| 633 | + <tspan x="1921.51" y="644.481">Primop:</tspan>
|
|
| 634 | + <tspan x="1921.51" y="660.481">throw exception</tspan>
|
|
| 635 | + </text>
|
|
| 636 | + </g>
|
|
| 637 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="1561.51" y1="598.6" x2="2021.52" y2="598.6"/>
|
|
| 638 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="618.6">
|
|
| 639 | + <tspan x="1561.51" y="618.6">RTS CMM</tspan>
|
|
| 640 | + </text>
|
|
| 641 | + <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="592.222">
|
|
| 642 | + <tspan x="1561.51" y="592.222">RTS C</tspan>
|
|
| 643 | + </text>
|
|
| 644 | + <g>
|
|
| 645 | + <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="579.591" x2="1721.51" y2="608.864"/>
|
|
| 646 | + <polygon style="fill: #000000" points="1721.51,616.364 1716.51,606.364 1721.51,608.864 1726.51,606.364 "/>
|
|
| 647 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,616.364 1716.51,606.364 1721.51,608.864 1726.51,606.364 "/>
|
|
| 648 | + </g>
|
|
| 649 | + <g>
|
|
| 650 | + <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1801.51,359.41 1801.51,358.6 1921.51,358.6 1921.51,608.864 "/>
|
|
| 651 | + <polygon style="fill: #000000" points="1921.51,616.364 1916.51,606.364 1921.51,608.864 1926.51,606.364 "/>
|
|
| 652 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1921.51,616.364 1916.51,606.364 1921.51,608.864 1926.51,606.364 "/>
|
|
| 653 | + </g>
|
|
| 654 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120.738" y="55">
|
|
| 655 | + <tspan x="-120.738" y="55"></tspan>
|
|
| 656 | + </text>
|
|
| 657 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="149">
|
|
| 658 | + <tspan x="-120" y="149"></tspan>
|
|
| 659 | + </text>
|
|
| 660 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-117.175" y="712">
|
|
| 661 | + <tspan x="-117.175" y="712"></tspan>
|
|
| 662 | + </text>
|
|
| 663 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-117.175" y="712">
|
|
| 664 | + <tspan x="-117.175" y="712"></tspan>
|
|
| 665 | + </text>
|
|
| 666 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="592">
|
|
| 667 | + <tspan x="583" y="592"></tspan>
|
|
| 668 | + </text>
|
|
| 669 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="712">
|
|
| 670 | + <tspan x="80" y="712"></tspan>
|
|
| 671 | + </text>
|
|
| 672 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="751">
|
|
| 673 | + <tspan x="1102" y="751"></tspan>
|
|
| 674 | + </text>
|
|
| 675 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1720.77" y="73.6">
|
|
| 676 | + <tspan x="1720.77" y="73.6"></tspan>
|
|
| 677 | + </text>
|
|
| 678 | + <g>
|
|
| 679 | + <polygon style="fill: #ffffff" points="1104,589.546 1208.51,624.256 1104,658.967 999.486,624.256 "/>
|
|
| 680 | + <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1104,589.546 1208.51,624.256 1104,658.967 999.486,624.256 "/>
|
|
| 681 | + <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1104" y="628.138">
|
|
| 682 | + <tspan x="1104" y="628.138">SQ ring not full</tspan>
|
|
| 683 | + </text>
|
|
| 684 | + </g>
|
|
| 685 | + <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1104" y="624.256">
|
|
| 686 | + <tspan x="1104" y="624.256"></tspan>
|
|
| 687 | + </text>
|
|
| 688 | + <text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="227">
|
|
| 689 | + <tspan x="483" y="227"></tspan>
|
|
| 690 | + </text>
|
|
| 691 | +</svg> |
| ... | ... | @@ -48,6 +48,8 @@ flag libdw |
| 48 | 48 | default: False
|
| 49 | 49 | flag libnuma
|
| 50 | 50 | default: False
|
| 51 | +flag liburing
|
|
| 52 | + default: True
|
|
| 51 | 53 | flag libzstd
|
| 52 | 54 | default: False
|
| 53 | 55 | flag static-libzstd
|
| ... | ... | @@ -231,6 +233,8 @@ library |
| 231 | 233 | extra-libraries: elf dw
|
| 232 | 234 | if flag(libnuma)
|
| 233 | 235 | extra-libraries: numa
|
| 236 | + if flag(liburing)
|
|
| 237 | + extra-libraries: uring
|
|
| 234 | 238 | if flag(libzstd)
|
| 235 | 239 | if flag(static-libzstd)
|
| 236 | 240 | if os(darwin)
|
| ... | ... | @@ -536,6 +540,7 @@ library |
| 536 | 540 | posix/Signals.c
|
| 537 | 541 | posix/Timeout.c
|
| 538 | 542 | posix/TTY.c
|
| 543 | + posix/URing.c
|
|
| 539 | 544 | -- ticker/*.c
|
| 540 | 545 | -- We don't want to compile posix/ticker/*.c, these will be #included
|
| 541 | 546 | -- from Ticker.c
|