Duncan Coutts pushed to branch wip/dcoutts/io-manager-selectbis at Glasgow Haskell Compiler / GHC

Commits:

17 changed files:

Changes:

  • changelog.d/select-io-manager
    1
    +section: rts
    
    2
    +issues:
    
    3
    +mrs: !16359
    
    4
    +synopsis:
    
    5
    +  New I/O manager based on select()
    
    6
    +description:
    
    7
    +  There is a new I/O manager on Posix systems based on select(). This exists
    
    8
    +  primarily to support macOS, where the poll() API does not work correctly
    
    9
    +  (specifically it is documented not to work for device files). It is the new
    
    10
    +  default I/O manager for the non-threaded RTS for the macOS platform.
    
    11
    +
    
    12
    +  This is intended to allow the legacy select I/O manager to be retired. It is
    
    13
    +  also a stop-gap measure until a kqueue I/O manager is added.
    
    14
    +
    
    15
    +  The new implementation is marginally faster in some cases. It scales better
    
    16
    +  for timers, O(log n) rather than O(m). For threads waiting on I/O it is
    
    17
    +  necessarily still O(n). If used to wait on fds > 1024 it will throw an IO
    
    18
    +  exception rather than terminating the RTS, as was the behaviour of the old
    
    19
    +  select I/O manager.

  • docs/users_guide/runtime_control.rst
    ... ... @@ -1441,15 +1441,30 @@ limited.
    1441 1441
     Currently the available I/O managers are:
    
    1442 1442
     
    
    1443 1443
     ================ ========= ============
    
    1444
    - Name            Platforms RTS way
    
    1444
    +I/O manager name Platforms RTS way
    
    1445 1445
     ================ ========= ============
    
    1446 1446
     ``select``       Posix     Non-threaded
    
    1447
    -``poll``         Posix     Non-threaded
    
    1447
    +``selectbis``    Posix     Non-threaded
    
    1448
    +``poll``         Posix(*)  Non-threaded
    
    1448 1449
     ``mio``          All       Threaded
    
    1449 1450
     ``win32-legacy`` Windows   Non-threaded
    
    1450 1451
     ``winio``        Windows   Both
    
    1451 1452
     ================ ========= ============
    
    1452 1453
     
    
    1454
    +(*) The ``poll`` I/O manager is not available on macOS due to platform
    
    1455
    +limitations.
    
    1456
    +
    
    1457
    +Currently the default I/O manager on each platform is:
    
    1458
    +
    
    1459
    +========= ============ ===================
    
    1460
    +Platform  RTS way      default I/O manager
    
    1461
    +========= ============ ===================
    
    1462
    +macOS     Non-threaded ``selectbis``
    
    1463
    +Posix     Non-threaded ``poll``
    
    1464
    +Windows   Non-threaded ``win32-legacy``
    
    1465
    +all       Threaded     ``mio``
    
    1466
    +========= ============ ===================
    
    1467
    +
    
    1453 1468
     .. rts-flag:: --io-manager=(name)
    
    1454 1469
     
    
    1455 1470
         Select the I/O manager to use. On some combinations of platform and
    
    ... ... @@ -1474,7 +1489,8 @@ This is because it uses a linked list for timers.
    1474 1489
     
    
    1475 1490
     This I/O manager is highly portable and its code is very mature: it is the I/O
    
    1476 1491
     manager that has been used by GHC in the single-threaded RTS on Posix platforms
    
    1477
    -since time immemorial.
    
    1492
    +since time immemorial. It is likely to be retired, once the ``poll`` and
    
    1493
    +``selectbis`` I/O managers are mature enough to cover all use cases.
    
    1478 1494
     
    
    1479 1495
     Timer resolution: on 64bit platforms it supports microsecond precision timers
    
    1480 1496
     while on 32bit platforms it only supports millisecond precision. Timer accuracy
    
    ... ... @@ -1485,6 +1501,29 @@ support 1024 open files. More specifically it supports file descriptors with
    1485 1501
     numerical value up to 1024 but no higher. It will terminate the RTS (and thus
    
    1486 1502
     typically the process) if this limit is exceeded.
    
    1487 1503
     
    
    1504
    +The ``selectbis`` I/O manager
    
    1505
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    1506
    +This I/O manager based on the classic Posix ``select()`` API. It supports
    
    1507
    +waiting on I/O readiness on non-blocking file descriptors (i.e. not disk files).
    
    1508
    +It is implemented within the RTS and is currently available only in the
    
    1509
    +non-threaded RTS.
    
    1510
    +
    
    1511
    +It scales poorly for I/O readiness notification: costing O(n) in the number of
    
    1512
    +threads that are waiting on I/O simultaneously. It scales well for timers:
    
    1513
    +most timer operations cost O(log n) in the number of simultaneous timers. This
    
    1514
    +is because it uses a heap data structure for timers.
    
    1515
    +
    
    1516
    +Timer resolution: this I/O manager supports microsecond precision timers.
    
    1517
    +
    
    1518
    +Limitation: on most platforms where it is available this I/O manager can only
    
    1519
    +support 1024 open files. More specifically it supports file descriptors with
    
    1520
    +numerical value up to 1024 but no higher. It will throw an IO exception if this
    
    1521
    +limit is exceeded.
    
    1522
    +
    
    1523
    +This I/O manager exists primarily to support macOS, due to ``poll()`` not
    
    1524
    +working properly on macOS, while ``select()`` does work. It's name reflects
    
    1525
    +the fact that it is the second I/O manager to be based on ``select()``.
    
    1526
    +
    
    1488 1527
     The ``poll`` I/O manager
    
    1489 1528
     ~~~~~~~~~~~~~~~~~~~~~~~~
    
    1490 1529
     
    
    ... ... @@ -1512,6 +1551,10 @@ limit can be adjusted using OS facilities (e.g. the ``ulimit`` command).
    1512 1551
     Exceeding this limit will cause the RTS (and thus typically the process) to
    
    1513 1552
     terminate.
    
    1514 1553
     
    
    1554
    +This I/O manager is not available on macOS due to the ``poll()`` API not
    
    1555
    +working for all file types on macOS. Specifically the macOS man page for
    
    1556
    +``poll`` documents that it does not work for device files.
    
    1557
    +
    
    1515 1558
     The ``mio`` I/O manager
    
    1516 1559
     ~~~~~~~~~~~~~~~~~~~~~~~
    
    1517 1560
     This I/O manager is based on several platform-specific APIs. It supports
    

  • libraries/base/src/GHC/RTS/Flags.hs
    ... ... @@ -390,10 +390,11 @@ internal_to_base_MiscFlags i@Internal.MiscFlags{..} =
    390 390
         internal_to_base_ioManager Internal.IoManagerFlagAuto        = IoManagerFlagAuto
    
    391 391
         internal_to_base_ioManager Internal.IoManagerFlagSelect      = IoManagerFlagSelect
    
    392 392
     #if __GLASGOW_HASKELL__ >= 1000
    
    393
    +    internal_to_base_ioManager Internal.IoManagerFlagSelectBis   = IoManagerFlagAuto
    
    393 394
         internal_to_base_ioManager Internal.IoManagerFlagPoll        = IoManagerFlagAuto
    
    394
    -      -- This is a lie, we cannot translate poll. We cannot translate
    
    395
    -      -- accurately because want to freeze the API of the the compat RTS flags
    
    396
    -      -- here. Using "auto" is the least bad translation.
    
    395
    +      -- This is a lie, we cannot translate these new I/O managers. We cannot
    
    396
    +      -- translate accurately because want to freeze the API of the the compat
    
    397
    +      -- RTS flags here. Using "auto" is the least bad translation.
    
    397 398
           -- https://github.com/haskell/core-libraries-committee/issues/362
    
    398 399
     #endif
    
    399 400
         internal_to_base_ioManager Internal.IoManagerFlagMIO         = IoManagerFlagMIO
    

  • libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs
    ... ... @@ -188,7 +188,7 @@ threadWait evt fd = mask_ $ do
    188 188
     
    
    189 189
     -- used at least by RTS in 'select()' IO manager backend
    
    190 190
     blockedOnBadFD :: SomeException
    
    191
    -blockedOnBadFD = toException $ errnoToIOError "awaitEvent" eBADF Nothing Nothing
    
    191
    +blockedOnBadFD = toException $ errnoToIOError "threadWaitRead/Write" eBADF Nothing Nothing
    
    192 192
     
    
    193 193
     threadWaitSTM :: Event -> Fd -> IO (STM (), IO ())
    
    194 194
     threadWaitSTM evt fd = mask_ $ do
    

  • libraries/ghc-internal/src/GHC/Internal/RTS/Flags.hsc
    ... ... @@ -184,6 +184,7 @@ data MiscFlags = MiscFlags
    184 184
     data IoManagerFlag =
    
    185 185
            IoManagerFlagAuto
    
    186 186
          | IoManagerFlagSelect        -- ^ Unix only, non-threaded RTS only
    
    187
    +     | IoManagerFlagSelectBis     -- ^ Unix only, non-threaded RTS only
    
    187 188
          | IoManagerFlagPoll          -- ^ Unix only, non-threaded RTS only
    
    188 189
          | IoManagerFlagMIO           -- ^ cross-platform, threaded RTS only
    
    189 190
          | IoManagerFlagWinIO         -- ^ Windows only
    

  • rts/IOManager.c
    ... ... @@ -33,6 +33,10 @@
    33 33
     #include "posix/Signals.h"
    
    34 34
     #endif
    
    35 35
     
    
    36
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    37
    +#include "posix/SelectBis.h"
    
    38
    +#endif
    
    39
    +
    
    36 40
     #if defined(IOMGR_ENABLED_POLL)
    
    37 41
     #include "posix/Poll.h"
    
    38 42
     #include "posix/Timeout.h"
    
    ... ... @@ -117,6 +121,14 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
    117 121
             return IOManagerAvailable;
    
    118 122
     #else
    
    119 123
             return IOManagerUnavailable;
    
    124
    +#endif
    
    125
    +    }
    
    126
    +    else if (strcmp("selectbis", iomgrstr) == 0) {
    
    127
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    128
    +        *flag = IO_MNGR_FLAG_SELECTBIS;
    
    129
    +        return IOManagerAvailable;
    
    130
    +#else
    
    131
    +        return IOManagerUnavailable;
    
    120 132
     #endif
    
    121 133
         }
    
    122 134
         else if (strcmp("poll", iomgrstr) == 0) {
    
    ... ... @@ -226,6 +238,8 @@ void selectIOManager(void)
    226 238
     #else // !defined(THREADED_RTS)
    
    227 239
     #if   defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
    
    228 240
                 iomgr_type = IO_MANAGER_SELECT;
    
    241
    +#elif defined(IOMGR_DEFAULT_NON_THREADED_SELECTBIS)
    
    242
    +            iomgr_type = IO_MANAGER_SELECTBIS;
    
    229 243
     #elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
    
    230 244
                 iomgr_type = IO_MANAGER_POLL;
    
    231 245
     #elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
    
    ... ... @@ -244,6 +258,12 @@ void selectIOManager(void)
    244 258
                 break;
    
    245 259
     #endif
    
    246 260
     
    
    261
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    262
    +        case IO_MNGR_FLAG_SELECTBIS:
    
    263
    +            iomgr_type = IO_MANAGER_SELECTBIS;
    
    264
    +            break;
    
    265
    +#endif
    
    266
    +
    
    247 267
     #if defined(IOMGR_ENABLED_POLL)
    
    248 268
             case IO_MNGR_FLAG_POLL:
    
    249 269
                 iomgr_type = IO_MANAGER_POLL;
    
    ... ... @@ -291,6 +311,10 @@ char * showIOManager(void)
    291 311
             case IO_MANAGER_SELECT:
    
    292 312
                 return "select";
    
    293 313
     #endif
    
    314
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    315
    +        case IO_MANAGER_SELECTBIS:
    
    316
    +            return "selectbis";
    
    317
    +#endif
    
    294 318
     #if defined(IOMGR_ENABLED_POLL)
    
    295 319
             case IO_MANAGER_POLL:
    
    296 320
                 return "poll";
    
    ... ... @@ -347,6 +371,12 @@ void initCapabilityIOManager(CapIOManager *iomgr)
    347 371
                 break;
    
    348 372
     #endif
    
    349 373
     
    
    374
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    375
    +        case IO_MANAGER_SELECTBIS:
    
    376
    +            initCapabilityIOManagerSelectBis(iomgr);
    
    377
    +            break;
    
    378
    +#endif
    
    379
    +
    
    350 380
     #if defined(IOMGR_ENABLED_POLL)
    
    351 381
             case IO_MANAGER_POLL:
    
    352 382
                 initCapabilityIOManagerPoll(iomgr);
    
    ... ... @@ -380,6 +410,12 @@ void freeCapabilityIOManager(CapIOManager *iomgr)
    380 410
                 break;
    
    381 411
     #endif
    
    382 412
     
    
    413
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    414
    +        case IO_MANAGER_SELECTBIS:
    
    415
    +            freeCapabilityIOManagerSelectBis(iomgr);
    
    416
    +            break;
    
    417
    +#endif
    
    418
    +
    
    383 419
     #if defined(IOMGR_ENABLED_POLL)
    
    384 420
             case IO_MANAGER_POLL:
    
    385 421
                 freeCapabilityIOManagerPoll(iomgr);
    
    ... ... @@ -399,10 +435,14 @@ void startIOManager(void)
    399 435
     
    
    400 436
         switch (iomgr_type) {
    
    401 437
     
    
    402
    -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
    
    438
    +#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_SELECTBIS) \
    
    439
    + || defined(IOMGR_ENABLED_POLL)
    
    403 440
     #if defined(IOMGR_ENABLED_SELECT)
    
    404 441
             case IO_MANAGER_SELECT:
    
    405 442
     #endif
    
    443
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    444
    +        case IO_MANAGER_SELECTBIS:
    
    445
    +#endif
    
    406 446
     #if defined(IOMGR_ENABLED_POLL)
    
    407 447
             case IO_MANAGER_POLL:
    
    408 448
     #endif
    
    ... ... @@ -479,6 +519,7 @@ restartIOManager(CapIOManager *iomgr, Capability **pcap)
    479 519
                 break;
    
    480 520
     #endif
    
    481 521
             /* The IO_MANAGER_SELECT needs no initialisation */
    
    522
    +        /* The IO_MANAGER_SELECTBIS needs no initialisation */
    
    482 523
             /* The IO_MANAGER_POLL needs no initialisation */
    
    483 524
     
    
    484 525
             /* No impl for any of the Windows I/O managers, since no forking. */
    
    ... ... @@ -570,8 +611,13 @@ void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr)
    570 611
                 break;
    
    571 612
     #endif
    
    572 613
     
    
    614
    +#if defined(IOMGR_ENABLED_SELECTBIS) || defined(IOMGR_ENABLED_POLL)
    
    615
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    616
    +        case IO_MANAGER_SELECTBIS:
    
    617
    +#endif
    
    573 618
     #if defined(IOMGR_ENABLED_POLL)
    
    574 619
             case IO_MANAGER_POLL:
    
    620
    +#endif
    
    575 621
                 markClosureTable(evac, user, &iomgr->aiop_table);
    
    576 622
                 evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
    
    577 623
                 break;
    
    ... ... @@ -599,8 +645,13 @@ void scavengeTSOIOManager(StgTSO *tso)
    599 645
                  * both of these are not GC pointers, so there is nothing to do.
    
    600 646
                  */
    
    601 647
     
    
    648
    +#if defined(IOMGR_ENABLED_SELECTBIS) || defined(IOMGR_ENABLED_POLL)
    
    649
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    650
    +        case IO_MANAGER_SELECTBIS:
    
    651
    +#endif
    
    602 652
     #if defined(IOMGR_ENABLED_POLL)
    
    603 653
             case IO_MANAGER_POLL:
    
    654
    +#endif
    
    604 655
                 /* BlockedOn{Read,Write} uses block_info.aiop
    
    605 656
                  * BlockedOnDelay        uses block_info.timeout
    
    606 657
                  * both of these are heap allocated, so we can do the same in all
    
    ... ... @@ -650,6 +701,11 @@ bool anyPendingTimeoutsOrIO(CapIOManager *iomgr)
    650 701
                     || (iomgr->sleeping_queue   != END_TSO_QUEUE);
    
    651 702
     #endif
    
    652 703
     
    
    704
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    705
    +        case IO_MANAGER_SELECTBIS:
    
    706
    +            return anyPendingTimeoutsOrIOSelectBis(iomgr);
    
    707
    +#endif
    
    708
    +
    
    653 709
     #if defined(IOMGR_ENABLED_POLL)
    
    654 710
             case IO_MANAGER_POLL:
    
    655 711
                 return anyPendingTimeoutsOrIOPoll(iomgr);
    
    ... ... @@ -709,6 +765,12 @@ void pollCompletedTimeoutsOrIO(CapIOManager *iomgr)
    709 765
               break;
    
    710 766
     #endif
    
    711 767
     
    
    768
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    769
    +        case IO_MANAGER_SELECTBIS:
    
    770
    +          pollCompletedTimeoutsOrIOSelectBis(iomgr);
    
    771
    +          break;
    
    772
    +#endif
    
    773
    +
    
    712 774
     #if defined(IOMGR_ENABLED_POLL)
    
    713 775
             case IO_MANAGER_POLL:
    
    714 776
               pollCompletedTimeoutsOrIOPoll(iomgr);
    
    ... ... @@ -743,6 +805,12 @@ bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr)
    743 805
               break;
    
    744 806
     #endif
    
    745 807
     
    
    808
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    809
    +        case IO_MANAGER_SELECTBIS:
    
    810
    +          completed = awaitCompletedTimeoutsOrIOSelectBis(iomgr);
    
    811
    +          break;
    
    812
    +#endif
    
    813
    +
    
    746 814
     #if defined(IOMGR_ENABLED_POLL)
    
    747 815
             case IO_MANAGER_POLL:
    
    748 816
               completed = awaitCompletedTimeoutsOrIOPoll(iomgr);
    
    ... ... @@ -784,6 +852,12 @@ void interruptIOManager(CapIOManager *iomgr)
    784 852
                 break;
    
    785 853
     #endif
    
    786 854
     
    
    855
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    856
    +        case IO_MANAGER_SELECTBIS:
    
    857
    +            interruptIOManagerSelectBis(iomgr);
    
    858
    +            break;
    
    859
    +#endif
    
    860
    +
    
    787 861
     #if defined(IOMGR_ENABLED_POLL)
    
    788 862
             case IO_MANAGER_POLL:
    
    789 863
                 interruptIOManagerPoll(iomgr);
    
    ... ... @@ -831,9 +905,12 @@ bool syncIOWaitReady(CapIOManager *iomgr,
    831 905
                 return true;
    
    832 906
             }
    
    833 907
     #endif
    
    908
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    909
    +        case IO_MANAGER_SELECTBIS:
    
    910
    +            return syncIOWaitReadySelectBis(iomgr, tso, rw, fd);
    
    911
    +#endif
    
    834 912
     #if defined(IOMGR_ENABLED_POLL)
    
    835 913
             case IO_MANAGER_POLL:
    
    836
    -            ASSERT(tso->why_blocked == NotBlocked);
    
    837 914
                 return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
    
    838 915
     #endif
    
    839 916
             default:
    
    ... ... @@ -854,6 +931,11 @@ void syncIOCancel(CapIOManager *iomgr, StgTSO *tso)
    854 931
                                         tso);
    
    855 932
                 break;
    
    856 933
     #endif
    
    934
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    935
    +        case IO_MANAGER_SELECTBIS:
    
    936
    +            syncIOCancelSelectBis(iomgr, tso);
    
    937
    +            break;
    
    938
    +#endif
    
    857 939
     #if defined(IOMGR_ENABLED_POLL)
    
    858 940
             case IO_MANAGER_POLL:
    
    859 941
                 syncIOCancelPoll(iomgr, tso);
    
    ... ... @@ -895,8 +977,13 @@ bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
    895 977
                 return true;
    
    896 978
             }
    
    897 979
     #endif
    
    980
    +#if defined(IOMGR_ENABLED_SELECTBIS) || defined(IOMGR_ENABLED_POLL)
    
    981
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    982
    +        case IO_MANAGER_SELECTBIS:
    
    983
    +#endif
    
    898 984
     #if defined(IOMGR_ENABLED_POLL)
    
    899 985
             case IO_MANAGER_POLL:
    
    986
    +#endif
    
    900 987
                 return syncDelayTimeout(iomgr, tso, us_delay);
    
    901 988
     #endif
    
    902 989
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -931,8 +1018,13 @@ void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso)
    931 1018
                 removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
    
    932 1019
                 break;
    
    933 1020
     #endif
    
    1021
    +#if defined(IOMGR_ENABLED_SELECTBIS) || defined(IOMGR_ENABLED_POLL)
    
    1022
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    1023
    +        case IO_MANAGER_SELECTBIS:
    
    1024
    +#endif
    
    934 1025
     #if defined(IOMGR_ENABLED_POLL)
    
    935 1026
             case IO_MANAGER_POLL:
    
    1027
    +#endif
    
    936 1028
                 syncDelayCancelTimeout(iomgr, tso);
    
    937 1029
                 break;
    
    938 1030
     #endif
    

  • rts/IOManager.h
    ... ... @@ -53,6 +53,9 @@ extern bool rts_IOManagerIsWin32Native;
    53 53
     #if defined(IOMGR_BUILD_SELECT) && !defined(THREADED_RTS)
    
    54 54
         #define IOMGR_ENABLED_SELECT
    
    55 55
     #endif
    
    56
    +#if defined(IOMGR_BUILD_SELECTBIS) && !defined(THREADED_RTS)
    
    57
    +    #define IOMGR_ENABLED_SELECTBIS
    
    58
    +#endif
    
    56 59
     #if defined(IOMGR_BUILD_POLL) && !defined(THREADED_RTS)
    
    57 60
         #define IOMGR_ENABLED_POLL
    
    58 61
     #endif
    
    ... ... @@ -95,6 +98,8 @@ extern bool rts_IOManagerIsWin32Native;
    95 98
     #else // !defined(THREADED_RTS)
    
    96 99
     #if   defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
    
    97 100
         #define IOMGR_DEFAULT_STR "select"
    
    101
    +#elif defined(IOMGR_DEFAULT_NON_THREADED_SELECTBIS)
    
    102
    +    #define IOMGR_DEFAULT_STR "selectbis"
    
    98 103
     #elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
    
    99 104
         #define IOMGR_DEFAULT_STR "poll"
    
    100 105
     #elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
    
    ... ... @@ -115,6 +120,11 @@ extern bool rts_IOManagerIsWin32Native;
    115 120
     #else
    
    116 121
         #define IOMGR_ENABLED_STR_SELECT ""
    
    117 122
     #endif
    
    123
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    124
    +    #define IOMGR_ENABLED_STR_SELECTBIS " selectbis"
    
    125
    +#else
    
    126
    +    #define IOMGR_ENABLED_STR_SELECTBIS ""
    
    127
    +#endif
    
    118 128
     #if defined(IOMGR_ENABLED_POLL)
    
    119 129
         #define IOMGR_ENABLED_STR_POLL " poll"
    
    120 130
     #else
    
    ... ... @@ -137,6 +147,7 @@ extern bool rts_IOManagerIsWin32Native;
    137 147
     #endif
    
    138 148
     #define IOMGRS_ENABLED_STR \
    
    139 149
               IOMGR_ENABLED_STR_SELECT \
    
    150
    +          IOMGR_ENABLED_STR_SELECTBIS \
    
    140 151
               IOMGR_ENABLED_STR_POLL \
    
    141 152
               IOMGR_ENABLED_STR_MIO \
    
    142 153
               IOMGR_ENABLED_STR_WINIO \
    
    ... ... @@ -150,6 +161,9 @@ typedef enum {
    150 161
     #if defined(IOMGR_ENABLED_SELECT)
    
    151 162
         IO_MANAGER_SELECT,
    
    152 163
     #endif
    
    164
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    165
    +    IO_MANAGER_SELECTBIS,
    
    166
    +#endif
    
    153 167
     #if defined(IOMGR_ENABLED_POLL)
    
    154 168
         IO_MANAGER_POLL,
    
    155 169
     #endif
    

  • rts/IOManagerInternals.h
    ... ... @@ -14,12 +14,19 @@
    14 14
     
    
    15 15
     #include "IOManager.h"
    
    16 16
     
    
    17
    -#if defined(IOMGR_ENABLED_POLL)
    
    18
    -#include <poll.h> /* for struct pollfd */
    
    17
    +#if defined(IOMGR_ENABLED_SELECTBIS) || defined(IOMGR_ENABLED_POLL)
    
    19 18
     #include "ClosureTable.h"
    
    20 19
     #include "TimeoutQueue.h"
    
    21 20
     #endif
    
    22 21
     
    
    22
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    23
    +#include <sys/select.h> /* for fd_set */
    
    24
    +#endif
    
    25
    +
    
    26
    +#if defined(IOMGR_ENABLED_POLL)
    
    27
    +#include <poll.h> /* for struct pollfd */
    
    28
    +#endif
    
    29
    +
    
    23 30
     #include "BeginPrivate.h"
    
    24 31
     
    
    25 32
     /* The per-capability data structures belonging to the I/O manager.
    
    ... ... @@ -46,19 +53,27 @@ struct _CapIOManager {
    46 53
         StgTSO *sleeping_queue;
    
    47 54
     #endif
    
    48 55
     
    
    49
    -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
    
    56
    +#if defined(IOMGR_ENABLED_SELECT) \
    
    57
    + || defined(IOMGR_ENABLED_SELECTBIS) \
    
    58
    + || defined(IOMGR_ENABLED_POLL)
    
    50 59
     #if defined(HAVE_PREEMPTION)
    
    51 60
         /* FDs for waking up the I/O manager when it is blocked waiting */
    
    52 61
         int interrupt_fd_r, interrupt_fd_w;
    
    53 62
     #endif
    
    54 63
     #endif
    
    55 64
     
    
    56
    -#if defined(IOMGR_ENABLED_POLL)
    
    65
    +#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_SELECTBIS)
    
    57 66
         /* AIOP and timeout collections shared by several I/O manager impls */
    
    58 67
         ClosureTable     aiop_table;
    
    59 68
         StgTimeoutQueue *timeout_queue;
    
    60 69
     #endif
    
    61 70
     
    
    71
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    72
    +    struct fd_table_entry { int fd; IOReadOrWrite rw; } *fd_table;
    
    73
    +    fd_set *rfds, *wfds;
    
    74
    +    int ncompletions_extra; /* extra completions for synchronous failures */
    
    75
    +#endif
    
    76
    +
    
    62 77
     #if defined(IOMGR_ENABLED_POLL)
    
    63 78
         /* Auxiliary table with size and indexes matching the aiop_table. This is
    
    64 79
          * aliased to the tail of the full poll table, which has a head entry for
    

  • rts/configure.ac
    ... ... @@ -368,6 +368,15 @@ GHC_IOMANAGER_ENABLE([select], [EnableIOManagerSelect], [IOMGR_BUILD_SELECT],
    368 368
                [AC_MSG_ERROR([sys/select.h required by select I/O manager])],[])
    
    369 369
        fi])
    
    370 370
     
    
    371
    +GHC_IOMANAGER_ENABLE([selectbis], [EnableIOManagerSelectBis], [IOMGR_BUILD_SELECTBIS],
    
    372
    +  [if test "$HostOS" = "mingw32"; then
    
    373
    +       EnableIOManagerSelectBis=NO
    
    374
    +   else
    
    375
    +       AC_CHECK_HEADER([sys/select.h],
    
    376
    +           [EnableIOManagerSelectBis=YES],
    
    377
    +           [AC_MSG_ERROR([sys/select.h required by selectbis I/O manager])],[])
    
    378
    +   fi])
    
    379
    +
    
    371 380
     GHC_IOMANAGER_ENABLE([poll], [EnableIOManagerPoll], [IOMGR_BUILD_POLL],
    
    372 381
       [if test "$HostOS" = "mingw32"; then
    
    373 382
            EnableIOManagerPoll=NO
    
    ... ... @@ -407,6 +416,7 @@ if test "$HostOS" = "mingw32"; then
    407 416
     else
    
    408 417
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [select], [EnableIOManagerSelect])
    
    409 418
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [poll], [EnableIOManagerPoll])
    
    419
    +  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [selectbis], [EnableIOManagerSelectBis])
    
    410 420
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
    
    411 421
     fi
    
    412 422
     GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY([IOManagerNonThreadedDefault],[non-threaded])
    
    ... ... @@ -419,6 +429,9 @@ dnl Now define CPP vars for the default ones (threaded and non-threaded)
    419 429
     GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    
    420 430
                                     [select], [IOMGR_DEFAULT_NON_THREADED_SELECT])
    
    421 431
     
    
    432
    +GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    
    433
    +                                [selectbis], [IOMGR_DEFAULT_NON_THREADED_SELECTBIS])
    
    434
    +
    
    422 435
     GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    
    423 436
                                     [poll], [IOMGR_DEFAULT_NON_THREADED_POLL])
    
    424 437
     
    

  • rts/include/rts/Flags.h
    ... ... @@ -258,6 +258,7 @@ typedef enum _IO_MANAGER_FLAG {
    258 258
     
    
    259 259
         /* All other choices pick only the requested one, with no fallback. */
    
    260 260
         IO_MNGR_FLAG_SELECT,          /* Unix only,    non-threaded RTS only */
    
    261
    +    IO_MNGR_FLAG_SELECTBIS,       /* Unix only,    non-threaded RTS only */
    
    261 262
         IO_MNGR_FLAG_POLL,            /* Unix only,    non-threaded RTS only */
    
    262 263
         IO_MNGR_FLAG_MIO,             /* cross-platform,   threaded RTS only */
    
    263 264
         IO_MNGR_FLAG_WINIO,           /* Windows only                        */
    

  • rts/posix/Poll.c
    ... ... @@ -133,7 +133,7 @@ the aiop_table, but still allows the full_poll_table to have an extra entry.
    133 133
     /* Forward declarations */
    
    134 134
     static bool enlargeTables(CapIOManager *iomgr);
    
    135 135
     static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    136
    -static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    136
    +static void removeFromTables(CapIOManager *iomgr, int i);
    
    137 137
     static void reportPollError(int res, nfds_t nfds) STG_NORETURN;
    
    138 138
     
    
    139 139
     
    
    ... ... @@ -224,7 +224,8 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso)
    224 224
         StgAsyncIOOp *aiop  = tso->block_info.aiop;
    
    225 225
         ASSERT(aiop->notify_type == NotifyTSO);
    
    226 226
         ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
    
    227
    -    ioCancel(iomgr, aiop);
    
    227
    +    removeFromTables(iomgr, aiop->index);
    
    228
    +    aiop->outcome = IOOpOutcomeCancelled;
    
    228 229
         /* We cannot use the normal notifyIOCompletion here. We are in the context
    
    229 230
          * of throwTo, interrupting a thread blocked on IO via an async exception.
    
    230 231
          * We don't put the TSO back on the run queue or change the why_blocked
    
    ... ... @@ -250,27 +251,13 @@ void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    250 251
          */
    
    251 252
         ASSERT(aiop->notify_type != NotifyTSO);
    
    252 253
         if (indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop) {
    
    253
    -        ioCancel(iomgr, aiop);
    
    254
    +        removeFromTables(iomgr, aiop->index);
    
    255
    +        aiop->outcome = IOOpOutcomeCancelled;
    
    254 256
             notifyIOCompletion(iomgr, aiop);
    
    255 257
         }
    
    256 258
     }
    
    257 259
     
    
    258 260
     
    
    259
    -static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    
    260
    -{
    
    261
    -    int ix = aiop->index;
    
    262
    -    int ix_from; int ix_to;
    
    263
    -    removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, ix,
    
    264
    -                              &ix_from, &ix_to);
    
    265
    -    if (ix_to != ix_from) {
    
    266
    -        StgAsyncIOOp *aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    267
    -        aiop_to->index = ix_to;
    
    268
    -        iomgr->aiop_poll_table[ix_to] = iomgr->aiop_poll_table[ix_from];
    
    269
    -    }
    
    270
    -    aiop->outcome = IOOpOutcomeCancelled;
    
    271
    -}
    
    272
    -
    
    273
    -
    
    274 261
     bool anyPendingTimeoutsOrIOPoll(CapIOManager *iomgr)
    
    275 262
     {
    
    276 263
         return !isEmptyTimeoutQueue(iomgr->timeout_queue)
    
    ... ... @@ -284,11 +271,16 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    284 271
         switch (aiop->notify_type) {
    
    285 272
             case NotifyTSO:
    
    286 273
             {
    
    274
    +            /* We should be guaranteed that the tso is still on the same
    
    275
    +             * cap because the tso was not on the run queue of any cap and
    
    276
    +             * so is not subject to thread migration.
    
    277
    +             */
    
    278
    +            StgTSO *tso = aiop->notify.tso;
    
    279
    +            ASSERT(tso->cap == iomgr->cap);
    
    287 280
                 if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
    
    288 281
                     /* The fd is invalid: raise an IOError exception in the blocked
    
    289 282
                      * thread. (See bug #4934 for what happens without this.)
    
    290 283
                      */
    
    291
    -                StgTSO *tso = aiop->notify.tso;
    
    292 284
                     debugTrace(DEBUG_iomanager,
    
    293 285
                                "Raising exception in thread %" FMT_StgThreadID
    
    294 286
                                " blocked on an invalid fd", tso->id);
    
    ... ... @@ -296,11 +288,6 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    296 288
                                (StgClosure *)blockedOnBadFD_closure,
    
    297 289
                                false, NULL);
    
    298 290
                 } else {
    
    299
    -                /* We should be guaranteed that the tso is still on the same
    
    300
    -                 * cap because the tso was not on the run queue of any cap and
    
    301
    -                 * so is not subject to thread migration.
    
    302
    -                 */
    
    303
    -                StgTSO *tso      = aiop->notify.tso;
    
    304 291
                     tso->why_blocked = NotBlocked;
    
    305 292
                     tso->_link       = END_TSO_QUEUE;
    
    306 293
                     pushOnRunQueue(iomgr->cap, tso);
    
    ... ... @@ -375,19 +362,7 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    375 362
                     aiop->result  = 0;
    
    376 363
                 }
    
    377 364
     
    
    378
    -            /* Remove from the completion table, preserving compactness, and
    
    379
    -             * apply the same compacting to the aiop_poll_table.
    
    380
    -             */
    
    381
    -            int ix_from; int ix_to;
    
    382
    -            removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, i,
    
    383
    -                                      &ix_from, &ix_to);
    
    384
    -            if (ix_to != ix_from) {
    
    385
    -                StgAsyncIOOp *aiop_to;
    
    386
    -                aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    387
    -                aiop_to->index = ix_to;
    
    388
    -                aiop_poll_table[ix_to] = aiop_poll_table[ix_from];
    
    389
    -            }
    
    390
    -
    
    365
    +            removeFromTables(iomgr, i);
    
    391 366
                 notifyIOCompletion(iomgr, aiop);
    
    392 367
                 n--;
    
    393 368
             } else {
    
    ... ... @@ -458,6 +433,10 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    458 433
                 reportPollError(res, nfds);
    
    459 434
             }
    
    460 435
         }
    
    436
    +
    
    437
    +#if defined(RTS_USER_SIGNALS)
    
    438
    +    startPendingSignalHandlers(iomgr->cap);
    
    439
    +#endif
    
    461 440
     }
    
    462 441
     
    
    463 442
     
    
    ... ... @@ -546,14 +525,13 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    546 525
                 // on and so we sould check for timeouts.
    
    547 526
     
    
    548 527
             } else if (errno == EINTR) {
    
    549
    -            /* We got interrupted by a signal. In the non-threaded RTS, if the
    
    550
    -             * signal is one of ours we need to return to the scheduler to let
    
    551
    -             * it handle it. Otherwise we would loop and keep waiting for I/O
    
    552
    -             * or timeouts, meaning we would block for a long time before the
    
    553
    -             * signal is serviced.
    
    554
    -             */
    
    528
    +            /* We got interrupted by a signal. */
    
    529
    +
    
    555 530
     #if defined(RTS_USER_SIGNALS)
    
    556
    -            if (startPendingSignalHandlers(iomgr->cap)) break;
    
    531
    +            /* Start any corresponding user signal handlers. If any, the run
    
    532
    +             * queue will become non-empty and we will drop out of the loop.
    
    533
    +             */
    
    534
    +            startPendingSignalHandlers(iomgr->cap);
    
    557 535
     #endif
    
    558 536
     
    
    559 537
                 /* We can also be interrupted by the shutdown signal handler, which
    
    ... ... @@ -628,4 +606,20 @@ static bool enlargeTables(CapIOManager *iomgr)
    628 606
         return true;
    
    629 607
     }
    
    630 608
     
    
    609
    +
    
    610
    +/* Remove from the completion table, preserving compactness, and apply the same
    
    611
    + * compacting to the aiop_poll_table.
    
    612
    + */
    
    613
    +static void removeFromTables(CapIOManager *iomgr, int ix)
    
    614
    +{
    
    615
    +    int ix_from; int ix_to;
    
    616
    +    removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, ix,
    
    617
    +                              &ix_from, &ix_to);
    
    618
    +    if (ix_to != ix_from) {
    
    619
    +        StgAsyncIOOp *aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    620
    +        aiop_to->index = ix_to;
    
    621
    +        iomgr->aiop_poll_table[ix_to] = iomgr->aiop_poll_table[ix_from];
    
    622
    +    }
    
    623
    +}
    
    624
    +
    
    631 625
     #endif /* IOMGR_ENABLED_POLL */

  • rts/posix/SelectBis.c
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team 2020-2026
    
    4
    + *
    
    5
    + * A second I/O manager based on the classic Unix select() system call.
    
    6
    + *
    
    7
    + * See SelectBis.h for the sad story of why this exists.
    
    8
    + *
    
    9
    + * ---------------------------------------------------------------------------*/
    
    10
    +
    
    11
    +#include "rts/PosixSource.h"
    
    12
    +#include "Rts.h"
    
    13
    +#include "RtsFlags.h" // needed by SET_HDR macro
    
    14
    +
    
    15
    +#include "IOManager.h" // defines IOMGR_ENABLED_SELECTBIS
    
    16
    +
    
    17
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    18
    +
    
    19
    +#include "Capability.h"
    
    20
    +#include "Threads.h"
    
    21
    +#include "Schedule.h"
    
    22
    +#include "Prelude.h"
    
    23
    +#include "RtsUtils.h"
    
    24
    +#include "rts/Time.h"
    
    25
    +#include "RaiseAsync.h"
    
    26
    +#include "Trace.h"
    
    27
    +
    
    28
    +#include "SelectBis.h"
    
    29
    +#include "RtsSignals.h"
    
    30
    +
    
    31
    +#include <sys/select.h>
    
    32
    +#include <errno.h>
    
    33
    +
    
    34
    +#include "IOManagerInternals.h"
    
    35
    +#include "Timeout.h"
    
    36
    +#include "FdWakeup.h"
    
    37
    +
    
    38
    +/******************************************************************************
    
    39
    +
    
    40
    +This I/O manager is based on the classic Unix select() system call.
    
    41
    +
    
    42
    +       int select(int nfds, fd_set *readfds, fd_set *writefds,
    
    43
    +                  fd_set *exceptfds, struct timeval *timeout);
    
    44
    +
    
    45
    +The select() call has various limits, quirks and slight differences between
    
    46
    +historical Unix variants.
    
    47
    +
    
    48
    +The basic idea is to collect a set of fds (represented as a bitset) that we are
    
    49
    +interested in: one for reads, one for writes. The call then queries for I/O
    
    50
    +readiness on all the fds in the read and write sets. The result is a set of fds
    
    51
    +that are ready to read from, and a set that are ready to write to. The same
    
    52
    +bitset representation is used for the output. Indeed a "fun" quirk of select()
    
    53
    +is that it mutates the fd sets it is passed, which means they either need to be
    
    54
    +built up each time, or copied. There is also an optional timeout if no fds are
    
    55
    +ready immediately. There is also an fd bitset for "exceptional conditions"
    
    56
    +which we do not use.
    
    57
    +
    
    58
    +There is of course no incremental behaviour here; this is a bulk one-off call
    
    59
    +with no persistent state. This has obvious scaling problems. The cost each time
    
    60
    +is O(n) in the maximum of the integer value of the fds of interest. There is
    
    61
    +also a maximum bitset size. On Linux this is 1024. This means select() cannot
    
    62
    +be used if the process uses more than that many open files, even if we're only
    
    63
    +interested in a few. On OSX the default limit is also 1024 but this can be
    
    64
    +raised or even managed dynamically, at the cost of more memory (and some
    
    65
    +non-standard code).
    
    66
    +
    
    67
    +That particular problem is solved by the later Unix poll() system call, which
    
    68
    +uses an array of the fds we are interested in, which means it not limited by
    
    69
    +the absolute value of the fds numbers (but it is still O(n) in how many fds we
    
    70
    +are interested in).
    
    71
    +
    
    72
    +We have some choice in how we process results. We want to find the intersection
    
    73
    +between the requests for notification of I/O readiness (coming from the Haskell
    
    74
    +threads) and the read and write bit sets. There's not much clever we can do to
    
    75
    +compute this intersection efficiently: we can either iterate over the bit sets
    
    76
    +or over the readiness requests. There is no obvious answer here. Typically
    
    77
    +there will be few results compared to the number of requests and a bitset scan
    
    78
    +could be fast. In practice we cannot portably scan the bitset efficiently (e.g.
    
    79
    +word at a time). Portably, we can only probe each bit at a time using FD_ISSET.
    
    80
    +Portability is the main reason to use select() rather than a more modern
    
    81
    +interface, so we have to take it seriously here. Furthermore, if we iterated
    
    82
    +over the bit sets we would have to maintain a mapping from fd to requests.
    
    83
    +
    
    84
    +In principle we also have the choice to maintain the read and write fd bit sets
    
    85
    +incrementally, or create them afresh each time we call select(). There is no
    
    86
    +asymptotic bonus to maintaining them incrementally since the whole thing is
    
    87
    +O(n) anyway. There could plausibly be some constant factor benefit. To maintain
    
    88
    +the fd bit sets incrementally we would need to maintain a mapping between
    
    89
    +requests and fds. This would also be an extra cost that would have to be
    
    90
    +outweighed by any saving.
    
    91
    +
    
    92
    +In the end we take the simple approach to constructing the bitset inputs and to
    
    93
    +results processing. We create the bit sets afresh each time from the collection
    
    94
    +of requests. For processing results we iterate over the requests and look up
    
    95
    +each one to see if it is in the appropriate result bitset. Along with each
    
    96
    +operation, we store the fd and whether we were interested in reading or writing.
    
    97
    +We iterate over the operations and use the fd and r/w information to construct
    
    98
    +the read and write bit sets.
    
    99
    +
    
    100
    +A particularly frustrating feature of select() is that if any single fd in any
    
    101
    +fd bitset is invalid (e.g. because the file was already closed) then select()
    
    102
    +fails and tells us there is a bad fd somewhere, but it has no way to indicate
    
    103
    +which fd was bad. This is really quite annoying as we then have to do a search
    
    104
    +through the fds to find which one was bad.
    
    105
    +
    
    106
    +The primary data structure for this I/O manager is a aiop_table which is a
    
    107
    +ClosureTable of AsyncIOOps. This table tracks the active I/O operations, with
    
    108
    +one entry per operation (corresponding to threads calling waitRead#/waitWrite#).
    
    109
    +We also track the fd for each operation and whether the operation is waiting on
    
    110
    +read or write readiness. This additional information is stored in the fd_table.
    
    111
    +The fd_table is maintained as an auxiliary table to the aiop_table, with table
    
    112
    +indexes matching the ClosureTable. So there is an entry in the aiop_table for
    
    113
    +each operation, and a corresponding entry in the fd_table at the same table
    
    114
    +index. The aiop_table and the fd_table are maintained incrementally, and with
    
    115
    +dense indexes.
    
    116
    +
    
    117
    +We also use a StgTimeoutQueue to track timeouts, and use the delay to the next
    
    118
    +timeout (if any) as the poll() timeout parameter.
    
    119
    +
    
    120
    +The CapIOManager structure for this I/O manager contains:
    
    121
    +
    
    122
    +    ClosureTable     aiop_table;
    
    123
    +    struct fd_table_entry { int fd; IOReadOrWrite rw } *fd_table;
    
    124
    +    StgTimeoutQueue *timeout_queue;
    
    125
    +    int interrupt_fd_r, interrupt_fd_w;
    
    126
    +
    
    127
    +******************************************************************************/
    
    128
    +
    
    129
    +/* Forward declarations */
    
    130
    +static bool enlargeTables(CapIOManager *iomgr);
    
    131
    +static void removeFromTables(CapIOManager *iomgr, int i);
    
    132
    +static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    133
    +static void reportSelectError(void) STG_NORETURN;
    
    134
    +static bool checkFdRange(int fd);
    
    135
    +static int  collectFdSets(CapIOManager *iomgr);
    
    136
    +static void processBadFds(CapIOManager *iomgr);
    
    137
    +
    
    138
    +
    
    139
    +void initCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    
    140
    +{
    
    141
    +    initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
    
    142
    +    iomgr->timeout_queue = emptyTimeoutQueue();
    
    143
    +
    
    144
    +#if defined(HAVE_PREEMPTION)
    
    145
    +    newFdWakeup(&iomgr->interrupt_fd_r, &iomgr->interrupt_fd_w);
    
    146
    +
    
    147
    +    /* Would never happen in a standalone process, but could plausibly happen
    
    148
    +     * if the RTS is used within another process that already has many open fds.
    
    149
    +     */
    
    150
    +    if (iomgr->interrupt_fd_r < 0 || iomgr->interrupt_fd_r >= (int)FD_SETSIZE ||
    
    151
    +        iomgr->interrupt_fd_w < 0 || iomgr->interrupt_fd_w >= (int)FD_SETSIZE) {
    
    152
    +        barf("initCapabilityIOManagerSelectBis: fds out of select range");
    
    153
    +    }
    
    154
    +#endif
    
    155
    +
    
    156
    +    iomgr->fd_table = NULL;
    
    157
    +    iomgr->rfds = stgMallocBytes(sizeof (fd_set), "IOManagerSelectBis");
    
    158
    +    iomgr->wfds = stgMallocBytes(sizeof (fd_set), "IOManagerSelectBis");
    
    159
    +    iomgr->ncompletions_extra = 0;
    
    160
    +}
    
    161
    +
    
    162
    +
    
    163
    +void freeCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    
    164
    +{
    
    165
    +    if (iomgr->fd_table) stgFree(iomgr->fd_table);
    
    166
    +    stgFree(iomgr->rfds);
    
    167
    +    stgFree(iomgr->wfds);
    
    168
    +#if defined(HAVE_PREEMPTION)
    
    169
    +    closeFdWakeup(iomgr->interrupt_fd_r, iomgr->interrupt_fd_w);
    
    170
    +#endif
    
    171
    +}
    
    172
    +
    
    173
    +
    
    174
    +/* Result is true on success, or false on allocation failure. */
    
    175
    +bool syncIOWaitReadySelectBis(CapIOManager *iomgr, StgTSO *tso,
    
    176
    +                              IOReadOrWrite rw, HsInt fd)
    
    177
    +{
    
    178
    +    StgAsyncIOOp *aiop;
    
    179
    +    aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp));
    
    180
    +    if (RTS_UNLIKELY(aiop == NULL)) return false;
    
    181
    +    SET_HDR(aiop, &stg_ASYNCIOOP_info, iomgr->cap->r.rCCCS);
    
    182
    +    aiop->notify.tso     = tso;
    
    183
    +    aiop->notify_type    = NotifyTSO;
    
    184
    +    aiop->live           = &stg_ASYNCIO_LIVE0_closure;
    
    185
    +    tso->why_blocked     = rw == IORead ? BlockedOnRead : BlockedOnWrite;
    
    186
    +    tso->block_info.aiop = aiop;
    
    187
    +    return asyncIOWaitReadySelectBis(iomgr, aiop, rw, fd);
    
    188
    +}
    
    189
    +
    
    190
    +/* Result is true on success, or false on allocation failure. */
    
    191
    +bool asyncIOWaitReadySelectBis(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    192
    +                               IOReadOrWrite rw, int fd)
    
    193
    +{
    
    194
    +    if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
    
    195
    +        bool ok = enlargeTables(iomgr);
    
    196
    +        if (RTS_UNLIKELY(!ok)) return false;
    
    197
    +    }
    
    198
    +
    
    199
    +    int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop);
    
    200
    +
    
    201
    +    /* We use the aiop_table and fd_table densely. */
    
    202
    +    ASSERT(ix == sizeClosureTable(&iomgr->aiop_table) - 1);
    
    203
    +
    
    204
    +    /* The syncIO wrapper or CMM primop filled in the notify and live fields,
    
    205
    +     * we fill the rest.
    
    206
    +     */
    
    207
    +    aiop->capno   = iomgr->cap->no;
    
    208
    +    aiop->index   = ix;
    
    209
    +    aiop->outcome = IOOpOutcomeInFlight;
    
    210
    +
    
    211
    +    /* Fill in the corresponding entry in the fd_table */
    
    212
    +    iomgr->fd_table[ix] = (struct fd_table_entry) {
    
    213
    +                            .fd = fd,
    
    214
    +                            .rw = rw
    
    215
    +                          };
    
    216
    +
    
    217
    +    if (!checkFdRange(fd)) {
    
    218
    +        /* We have a synchronous failure, but the primop is not set up to report
    
    219
    +         * exceptions. We cannot report async exceptions to the caller here
    
    220
    +         * since the thread stack is not in the right state (so we cannot use
    
    221
    +         * notifyIOCompletion). So instead we mark the aiop as failed now, but
    
    222
    +         * we report the failure later when we poll for completed I/O.
    
    223
    +         */
    
    224
    +        aiop->outcome = IOOpOutcomeFailed;
    
    225
    +        aiop->error   = EBADF;
    
    226
    +        /* completions for synchronous failures to report asynchronously */
    
    227
    +        iomgr->ncompletions_extra++;
    
    228
    +    };
    
    229
    +
    
    230
    +    return true;
    
    231
    +}
    
    232
    +
    
    233
    +
    
    234
    +void syncIOCancelSelectBis(CapIOManager *iomgr, StgTSO *tso)
    
    235
    +{
    
    236
    +    StgAsyncIOOp *aiop  = tso->block_info.aiop;
    
    237
    +    ASSERT(aiop->notify_type == NotifyTSO);
    
    238
    +    ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
    
    239
    +    removeFromTables(iomgr, aiop->index);
    
    240
    +    aiop->outcome = IOOpOutcomeCancelled;
    
    241
    +    /* We cannot use the normal notifyIOCompletion here. We are in the context
    
    242
    +     * of throwTo, interrupting a thread blocked on IO via an async exception.
    
    243
    +     * We don't put the TSO back on the run queue or change the why_blocked
    
    244
    +     * status, as that is done by removeFromQueues (in the throwTo* functions).
    
    245
    +     */
    
    246
    +    tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
    
    247
    +
    
    248
    +    /* We are in the TSO case, where the aiop was only reachable from the TSO
    
    249
    +     * itself, and thus it is now no longer be reachable at all.
    
    250
    +     */
    
    251
    +    IF_NONMOVING_WRITE_BARRIER_ENABLED {
    
    252
    +        updateRemembSetPushClosure(iomgr->cap, (StgClosure *)aiop);
    
    253
    +    }
    
    254
    +}
    
    255
    +
    
    256
    +
    
    257
    +void asyncIOCancelSelectBis(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    
    258
    +{
    
    259
    +    /* We can reliably determine if the aiop is still in progress by checking
    
    260
    +     * if the aiop_table still points to this aiop object. This is reliable
    
    261
    +     * because each aiop is GC heap allocated, so cannot be recycled until it
    
    262
    +     * is no longer retained by the application.
    
    263
    +     */
    
    264
    +    ASSERT(aiop->notify_type != NotifyTSO);
    
    265
    +    if (indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop) {
    
    266
    +        removeFromTables(iomgr, aiop->index);
    
    267
    +        aiop->outcome = IOOpOutcomeCancelled;
    
    268
    +        notifyIOCompletion(iomgr, aiop);
    
    269
    +    }
    
    270
    +}
    
    271
    +
    
    272
    +
    
    273
    +bool anyPendingTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    
    274
    +{
    
    275
    +    return !isEmptyTimeoutQueue(iomgr->timeout_queue)
    
    276
    +        || !isEmptyClosureTable(&iomgr->aiop_table);
    
    277
    +}
    
    278
    +
    
    279
    +
    
    280
    +static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    
    281
    +{
    
    282
    +    ASSERT(aiop->outcome != IOOpOutcomeInFlight);
    
    283
    +    switch (aiop->notify_type) {
    
    284
    +        case NotifyTSO:
    
    285
    +        {
    
    286
    +            /* We should be guaranteed that the tso is still on the same
    
    287
    +             * cap because the tso was not on the run queue of any cap and
    
    288
    +             * so is not subject to thread migration.
    
    289
    +             */
    
    290
    +            StgTSO *tso = aiop->notify.tso;
    
    291
    +            ASSERT(tso->cap == iomgr->cap);
    
    292
    +            if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
    
    293
    +                /* The fd is invalid: raise an IOError exception in the blocked
    
    294
    +                 * thread. (See bug #4934 for what happens without this.)
    
    295
    +                 */
    
    296
    +                debugTrace(DEBUG_iomanager,
    
    297
    +                           "Raising exception in thread %" FMT_StgThreadID
    
    298
    +                           " blocked on an invalid fd", tso->id);
    
    299
    +                raiseAsync(iomgr->cap, tso,
    
    300
    +                           (StgClosure *)blockedOnBadFD_closure,
    
    301
    +                           false, NULL);
    
    302
    +            } else {
    
    303
    +                tso->why_blocked = NotBlocked;
    
    304
    +                tso->_link       = END_TSO_QUEUE;
    
    305
    +                pushOnRunQueue(iomgr->cap, tso);
    
    306
    +            }
    
    307
    +            /* For the TSO case, the aiop was only reachable from the TSO
    
    308
    +             * itself, and thus it is now no longer be reachable at all.
    
    309
    +             */
    
    310
    +            IF_NONMOVING_WRITE_BARRIER_ENABLED {
    
    311
    +                updateRemembSetPushClosure(iomgr->cap, (StgClosure *)aiop);
    
    312
    +            }
    
    313
    +            break;
    
    314
    +        }
    
    315
    +        case NotifyMVar:
    
    316
    +            barf("selectbis iomgr: MVar notification not yet supported");
    
    317
    +            break;
    
    318
    +
    
    319
    +        case NotifyTVar:
    
    320
    +            barf("selectbis iomgr: TVar notification not yet supported");
    
    321
    +            break;
    
    322
    +    }
    
    323
    +}
    
    324
    +
    
    325
    +
    
    326
    +static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    
    327
    +{
    
    328
    +    /* We want to find the intersection between the sets of ready fds returned
    
    329
    +     * by select() and the aiop_table. Given how select() represents
    
    330
    +     * things there's no particularly efficient way to do it.
    
    331
    +     *
    
    332
    +     * We just go through the whole aiop_table and look up each one in
    
    333
    +     * the read or write fd_set to see if they completed. Note that here is
    
    334
    +     * where we rely on the aiop_table being dense so we can iterate
    
    335
    +     * over the entries. We can short-cut if we hit the ncompletions before
    
    336
    +     * getting to the end of the table.
    
    337
    +     */
    
    338
    +    debugTrace(DEBUG_iomanager, "processIOCompletions(ncompletions = %d)",
    
    339
    +                                ncompletions);
    
    340
    +
    
    341
    +    bool interrupt = false;
    
    342
    +#if defined(HAVE_PREEMPTION)
    
    343
    +    /* If the interrupt_fd_r is ready, collect it */
    
    344
    +    if (FD_ISSET(iomgr->interrupt_fd_r, iomgr->rfds)) {
    
    345
    +        ASSERT(iomgr->full_poll_table[0].fd == iomgr->interrupt_fd_r);
    
    346
    +        collectFdWakeup(iomgr->interrupt_fd_r);
    
    347
    +        ncompletions--;
    
    348
    +        interrupt = true;
    
    349
    +        debugTrace(DEBUG_iomanager, "Received interrupt in poll I/O manager");
    
    350
    +    }
    
    351
    +#endif
    
    352
    +
    
    353
    +    struct fd_table_entry *fd_table = iomgr->fd_table;
    
    354
    +    int n = ncompletions;
    
    355
    +    int i = 0;
    
    356
    +    while (n > 0) {
    
    357
    +        ASSERT(i < sizeClosureTable(&iomgr->aiop_table));
    
    358
    +
    
    359
    +        StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, i);
    
    360
    +        int           fd   = fd_table[i].fd;
    
    361
    +        IOReadOrWrite rw   = fd_table[i].rw;
    
    362
    +
    
    363
    +        if (RTS_UNLIKELY(aiop->outcome == IOOpOutcomeFailed)) {
    
    364
    +            /* The synchronous failure case, see ncompletions_extra. */
    
    365
    +        } else if (rw == IORead ? FD_ISSET(fd, iomgr->rfds)
    
    366
    +                                : FD_ISSET(fd, iomgr->wfds)) {
    
    367
    +            aiop->outcome = IOOpOutcomeSuccess;
    
    368
    +            aiop->result  = 0;
    
    369
    +        } else {
    
    370
    +            /* You'd expect incrementing the table index to be unconditional,
    
    371
    +             * but we don't increment the index if we did process the entry,
    
    372
    +             * because using removeFromTables means we'll move an entry from
    
    373
    +             * the end of the table into the index i.
    
    374
    +             */
    
    375
    +            i++;
    
    376
    +            continue; /* skip the steps below */
    
    377
    +        }
    
    378
    +        removeFromTables(iomgr, i);
    
    379
    +        notifyIOCompletion(iomgr, aiop);
    
    380
    +        n--;
    
    381
    +    }
    
    382
    +    return interrupt;
    
    383
    +}
    
    384
    +
    
    385
    +
    
    386
    +void pollCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    
    387
    +{
    
    388
    +    if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
    
    389
    +        Time now = getProcessElapsedTime();
    
    390
    +        processTimeoutCompletions(iomgr, now);
    
    391
    +    }
    
    392
    +
    
    393
    +    if (!isEmptyClosureTable(&iomgr->aiop_table)) {
    
    394
    +        /* Prepare to poll for I/O readiness: collect all of the fd's that
    
    395
    +         * we're interested in.
    
    396
    +         */
    
    397
    +        int maxfd = collectFdSets(iomgr);
    
    398
    +
    
    399
    +        /* Poll for I/O readiness, without waiting. */
    
    400
    +        struct timeval tv = (struct timeval) { .tv_sec = 0, .tv_usec = 0 };
    
    401
    +        int res = select(maxfd+1, iomgr->rfds, iomgr->wfds, NULL, &tv);
    
    402
    +        if (res == 0 && iomgr->ncompletions_extra == 0) {
    
    403
    +            /* There is no I/O ready. We'll return to the scheduler. */
    
    404
    +
    
    405
    +        } else if (res > 0 || iomgr->ncompletions_extra > 0) {
    
    406
    +            /* Extra completions for synchronous failures to report */
    
    407
    +            int ncompletions = res + iomgr->ncompletions_extra;
    
    408
    +            iomgr->ncompletions_extra = 0;
    
    409
    +
    
    410
    +            ASSERT(ncompletions <= sizeClosureTable(&iomgr->aiop_table));
    
    411
    +            processIOCompletions(iomgr, ncompletions);
    
    412
    +
    
    413
    +        } else if (errno == EBADF) {
    
    414
    +            processBadFds(iomgr);
    
    415
    +
    
    416
    +        } else if (errno == EINTR) {
    
    417
    +          /* We got interrupted by a signal. This is unlikely since we asked
    
    418
    +           * select() not to wait, but if so we'll return to the scheduler.
    
    419
    +           */
    
    420
    +
    
    421
    +        } else {
    
    422
    +            reportSelectError();
    
    423
    +        }
    
    424
    +    }
    
    425
    +
    
    426
    +#if defined(RTS_USER_SIGNALS)
    
    427
    +    startPendingSignalHandlers(iomgr->cap);
    
    428
    +#endif
    
    429
    +}
    
    430
    +
    
    431
    +
    
    432
    +bool awaitCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    
    433
    +{
    
    434
    +    bool interrupt = false; /* got woken up via interruptIOManager */
    
    435
    +
    
    436
    +    /* Loop until we've woken up some threads. This loop is needed because the
    
    437
    +     * select() timing isn't accurate, we sometimes sleep for a while but not
    
    438
    +     * long enough to wake up a thread in a threadDelay. Or we may need to
    
    439
    +     * sleep multiple times if we need to sleep longer than the maximum timeout
    
    440
    +     * that select() supports.
    
    441
    +     */
    
    442
    +    do {
    
    443
    +        /* We do /not/ require that there be pending I/O or pending timers.
    
    444
    +         * If there is neither, it's because the scheduler wants us to wait
    
    445
    +         * on signals only.
    
    446
    +         */
    
    447
    +
    
    448
    +        Time now = getProcessElapsedTime();
    
    449
    +        processTimeoutCompletions(iomgr, now);
    
    450
    +
    
    451
    +        /* If we didn't wake any threads due to expiring timeouts, then we need
    
    452
    +         * to wait on I/O. Or to put it another way, even if we did wake some
    
    453
    +         * threads, we'll still poll (but not wait) for I/O. This is to ensure
    
    454
    +         * we avoid starving threads blocked on I/O.
    
    455
    +         */
    
    456
    +        bool wait = emptyRunQueue(iomgr->cap);
    
    457
    +
    
    458
    +        /* If we have failures to report, we must not block. */
    
    459
    +        if (iomgr->ncompletions_extra > 0) {
    
    460
    +            wait = false;
    
    461
    +        }
    
    462
    +
    
    463
    +        /* Prepare to poll for I/O readiness: collect all of the fd's that
    
    464
    +         * we're interested in.
    
    465
    +         */
    
    466
    +        int maxfd = collectFdSets(iomgr);
    
    467
    +
    
    468
    +        /* Decide if we are going to wait if no I/O is ready, either:
    
    469
    +         * poll only, wait indefinitely, or wait until a timeout.
    
    470
    +         */
    
    471
    +        struct timeval tv, *timeout_us;
    
    472
    +        timeout_us = timeoutInMicroseconds(iomgr, wait, now, &tv);
    
    473
    +
    
    474
    +        /* Check for I/O readiness, possibly waiting. */
    
    475
    +        int res = select(maxfd+1, iomgr->rfds, iomgr->wfds, NULL, timeout_us);
    
    476
    +
    
    477
    +        if (res == 0 && iomgr->ncompletions_extra == 0) {
    
    478
    +            /* Success but there is no I/O ready. This can happen either if we
    
    479
    +             * were not blocking or were in a timed wait and the timeout
    
    480
    +             * occurred before any I/O became ready. Either way, the do-while
    
    481
    +             * loop condition will handle it.
    
    482
    +             */
    
    483
    +            ASSERT(timeout_us != NULL);
    
    484
    +
    
    485
    +        } else if (res > 0 || iomgr->ncompletions_extra > 0) {
    
    486
    +            /* Extra completions for synchronous failures to report */
    
    487
    +            int ncompletions = res + iomgr->ncompletions_extra;
    
    488
    +            iomgr->ncompletions_extra = 0;
    
    489
    +
    
    490
    +            ASSERT(ncompletions <= sizeClosureTable(&iomgr->aiop_table));
    
    491
    +            interrupt = processIOCompletions(iomgr, ncompletions);
    
    492
    +            // FIXME: do we also need to check for timeout completions now?
    
    493
    +            // we have a non-empty queue, but if !wait then we have also moved
    
    494
    +            // on and so we sould check for timeouts.
    
    495
    +
    
    496
    +        } else if (errno == EINTR) {
    
    497
    +            /* We got interrupted by a signal. */
    
    498
    +
    
    499
    +#if defined(RTS_USER_SIGNALS)
    
    500
    +            /* Start any corresponding user signal handlers. If any, the run
    
    501
    +             * queue will become non-empty and we will drop out of the loop.
    
    502
    +             */
    
    503
    +            startPendingSignalHandlers(iomgr->cap);
    
    504
    +#endif
    
    505
    +
    
    506
    +            /* We can also be interrupted by the shutdown signal handler, which
    
    507
    +             * will set sched_state and so cause us to drop out of the loop.
    
    508
    +             *
    
    509
    +             * For any other interruption (e.g. timer) we will go round the
    
    510
    +             * do-while loop again.
    
    511
    +             */
    
    512
    +
    
    513
    +        } else if (errno == EBADF) {
    
    514
    +            processBadFds(iomgr);
    
    515
    +
    
    516
    +        } else {
    
    517
    +            reportSelectError();
    
    518
    +        }
    
    519
    +
    
    520
    +    } while (emptyRunQueue(iomgr->cap)
    
    521
    +         && !interrupt
    
    522
    +         && (getSchedState() == SCHED_RUNNING));
    
    523
    +    return !interrupt;
    
    524
    +}
    
    525
    +
    
    526
    +
    
    527
    +static void reportSelectError()
    
    528
    +{
    
    529
    +    sysErrorBelch("select() failed");
    
    530
    +    stg_exit(EXIT_FAILURE);
    
    531
    +}
    
    532
    +
    
    533
    +
    
    534
    +static void processBadFds(CapIOManager *iomgr)
    
    535
    +{
    
    536
    +    /* This is extremely tiresome. The select() call fails with EBADF if any
    
    537
    +     * fd is invalid (usually closed), but it does not tell us which one.
    
    538
    +     * So we have to loop through them to find the offending fd.
    
    539
    +     *
    
    540
    +     * This will only find the first bad fd, so the caller must cope with
    
    541
    +     * there still being bad fds after this.
    
    542
    +     */
    
    543
    +
    
    544
    +    fd_set rfds, wfds;
    
    545
    +    FD_ZERO(&rfds);
    
    546
    +    FD_ZERO(&wfds);
    
    547
    +
    
    548
    +    struct fd_table_entry *fd_table = iomgr->fd_table;
    
    549
    +    int  nentries = sizeClosureTable(&iomgr->aiop_table);
    
    550
    +    for (int n = 0; n < nentries; n++) {
    
    551
    +        int           fd = fd_table[n].fd;
    
    552
    +        IOReadOrWrite rw = fd_table[n].rw;
    
    553
    +
    
    554
    +        struct timeval tv = { .tv_sec = 0, .tv_usec = 0 };
    
    555
    +        int res;
    
    556
    +        if (rw == IORead) {
    
    557
    +            FD_SET(fd, &rfds);
    
    558
    +            res = select(fd+1, &rfds, NULL, NULL, &tv);
    
    559
    +            FD_CLR(fd, &rfds);
    
    560
    +        } else {
    
    561
    +            FD_SET(fd, &wfds);
    
    562
    +            res = select(fd+1, NULL, &wfds, NULL, &tv);
    
    563
    +            FD_CLR(fd, &wfds);
    
    564
    +        }
    
    565
    +        if (res == 0) {
    
    566
    +            continue;
    
    567
    +
    
    568
    +        } else if (errno == EBADF) {
    
    569
    +            StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, n);
    
    570
    +            aiop->outcome = IOOpOutcomeFailed;
    
    571
    +            aiop->error   = EBADF;
    
    572
    +            removeFromTables(iomgr, n);
    
    573
    +            notifyIOCompletion(iomgr, aiop);
    
    574
    +            /* There is /probably/ only one bad fd at once, so we abort the
    
    575
    +             * search here. If we are unlucky and there are several bad fds
    
    576
    +             * then the caller will just loop round again.
    
    577
    +             */
    
    578
    +
    
    579
    +            return;
    
    580
    +
    
    581
    +        } else if (errno == EINTR) {
    
    582
    +            /* Unlikely, since we did a non-blocking select(). Try again. */
    
    583
    +            n--;
    
    584
    +            continue;
    
    585
    +
    
    586
    +        } else {
    
    587
    +            reportSelectError();
    
    588
    +        }
    
    589
    +    }
    
    590
    +}
    
    591
    +
    
    592
    +
    
    593
    +void interruptIOManagerSelectBis(CapIOManager *iomgr)
    
    594
    +{
    
    595
    +#if defined(HAVE_PREEMPTION)
    
    596
    +    sendFdWakeup(iomgr->interrupt_fd_w);
    
    597
    +#endif
    
    598
    +}
    
    599
    +
    
    600
    +
    
    601
    +/* Helper function to double the size of the aiop_table and fd_table.
    
    602
    + */
    
    603
    +static bool enlargeTables(CapIOManager *iomgr)
    
    604
    +{
    
    605
    +    int oldcapacity = capacityClosureTable(&iomgr->aiop_table);
    
    606
    +    int newcapacity = (oldcapacity == 0) ? 1 : (oldcapacity * 2);
    
    607
    +
    
    608
    +    bool ok = enlargeClosureTable(iomgr->cap, &iomgr->aiop_table, newcapacity);
    
    609
    +    if (RTS_UNLIKELY(!ok)) return false;
    
    610
    +
    
    611
    +    /* Update the auxiliary fd_table to match */
    
    612
    +    iomgr->fd_table =
    
    613
    +        stgReallocBytes(iomgr->fd_table,
    
    614
    +                        sizeof(struct fd_table_entry) * newcapacity,
    
    615
    +                        "SelectBis.c: enlargeTables");
    
    616
    +
    
    617
    +    /* Initialise the new part of the fd_table */
    
    618
    +    struct fd_table_entry *fd_table = iomgr->fd_table;
    
    619
    +    for (int i = oldcapacity; i < newcapacity; i++) {
    
    620
    +        fd_table[i] = (struct fd_table_entry) {
    
    621
    +                        .fd = -1,
    
    622
    +                        .rw = 0
    
    623
    +                      };
    
    624
    +    }
    
    625
    +    return true;
    
    626
    +}
    
    627
    +
    
    628
    +
    
    629
    +/* Remove from the completion table, preserving compactness, and apply the same
    
    630
    + * compacting to the fd_table.
    
    631
    + */
    
    632
    +static void removeFromTables(CapIOManager *iomgr, int ix)
    
    633
    +{
    
    634
    +    int ix_from; int ix_to;
    
    635
    +    removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, ix,
    
    636
    +                              &ix_from, &ix_to);
    
    637
    +    if (ix_to != ix_from) {
    
    638
    +        StgAsyncIOOp *aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    639
    +        aiop_to->index = ix_to;
    
    640
    +        iomgr->fd_table[ix_to]   = iomgr->fd_table[ix_from];
    
    641
    +        iomgr->fd_table[ix_from] = (struct fd_table_entry) {
    
    642
    +                                     .fd = -1,
    
    643
    +                                     .rw = 0
    
    644
    +                                   };
    
    645
    +    }
    
    646
    +}
    
    647
    +
    
    648
    +
    
    649
    +static int collectFdSets(CapIOManager *iomgr)
    
    650
    +{
    
    651
    +    int maxfd    = -1;
    
    652
    +    int nentries = sizeClosureTable(&iomgr->aiop_table);
    
    653
    +    struct fd_table_entry *fd_table = iomgr->fd_table;
    
    654
    +
    
    655
    +    /* In principle we could optimise this slightly by not resetting the
    
    656
    +     * whole of each fdset, by assuming that select() does not modify
    
    657
    +     * entries above maxfd. This is probably not worth doing however, since
    
    658
    +     * this I/O manager is supposed to be portable and is expected to be slow.
    
    659
    +     */
    
    660
    +    FD_ZERO(iomgr->rfds);
    
    661
    +    FD_ZERO(iomgr->wfds);
    
    662
    +
    
    663
    +#if defined(HAVE_PREEMPTION)
    
    664
    +    /* We're always interested in our interrupt fd */
    
    665
    +    {
    
    666
    +        int fd = iomgr->interrupt_fd_r;
    
    667
    +        maxfd = (fd > maxfd) ? fd : maxfd;
    
    668
    +        FD_SET(fd, iomgr->rfds);
    
    669
    +    }
    
    670
    +#endif
    
    671
    +
    
    672
    +    for (int i = 0; i < nentries; i++) {
    
    673
    +        int           fd = fd_table[i].fd;
    
    674
    +        IOReadOrWrite rw = fd_table[i].rw;
    
    675
    +        ASSERT(fd != -1); // uninitialised
    
    676
    +
    
    677
    +        // Skip aiops that we already know are failed
    
    678
    +        StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, i);
    
    679
    +        if (RTS_UNLIKELY(aiop->outcome == IOOpOutcomeFailed)) continue;
    
    680
    +
    
    681
    +        if (rw == IORead) {
    
    682
    +            FD_SET(fd, iomgr->rfds);
    
    683
    +        } else {
    
    684
    +            FD_SET(fd, iomgr->wfds);
    
    685
    +        }
    
    686
    +        maxfd = (fd > maxfd) ? fd : maxfd;
    
    687
    +    }
    
    688
    +    return maxfd;
    
    689
    +}
    
    690
    +
    
    691
    +
    
    692
    +/* Helper function to check if the fd is out of range for select().
    
    693
    + */
    
    694
    +static bool checkFdRange(int fd)
    
    695
    +{
    
    696
    +    /* On older FreeBSDs, FD_SETSIZE is unsigned. Cast it to signed int
    
    697
    +     * in order to switch off the 'comparison between signed and
    
    698
    +     * unsigned error message
    
    699
    +     * Newer versions of FreeBSD have switched to unsigned int:
    
    700
    +     *   https://github.com/freebsd/freebsd/commit/12ae7f74a071f0439763986026525094a7032dfd
    
    701
    +     *   http://fa.freebsd.cvs-all.narkive.com/bCWNHbaC/svn-commit-r265051-head-sys-sys
    
    702
    +     * So the (int) cast should be removed across the code base once
    
    703
    +     * GHC requires a version of FreeBSD that has that change in it.
    
    704
    +     */
    
    705
    +    return ((fd >= 0) && (fd < (int)FD_SETSIZE));
    
    706
    +    /* TODO: on several platforms, it is possible to use a larger fd set size.
    
    707
    +       For example on OSX:
    
    708
    +       https://code.saghul.net/2016/05/libuv-internals-the-osx-select2-trick/
    
    709
    +       And probably similar on other platforms. It basically amounts to looking
    
    710
    +       through the representation abstraction of fd_set and to know that it is
    
    711
    +       indeed a bit set, and then we can simply allocate it and manipulte it
    
    712
    +       ourselves. We could do this, dynamically (re-)allocate the size.
    
    713
    +     */
    
    714
    +}
    
    715
    +
    
    716
    +#endif /* IOMGR_ENABLED_SELECTBIS */

  • rts/posix/SelectBis.h
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team 2020-2026
    
    4
    + *
    
    5
    + * A second I/O manager based on the classic Unix select() system call.
    
    6
    + *
    
    7
    + * This I/O manager is called "selectbis", because it is the second such I/O
    
    8
    + * manager based on select(). The historic implementation is named "select"
    
    9
    + * and lives in Select.{c,h}. This I/O manager exists for the benefit of users
    
    10
    + * of Apple products.
    
    11
    + *
    
    12
    + * The poll I/O manger _should_ be the portable baseline posix I/O manager.
    
    13
    + * Unfortunately Mac OSX has a buggy implementation of poll(). The OSX man
    
    14
    + * page documents this as:
    
    15
    + *
    
    16
    + * > BUGS The poll() system call currently does not support devices.
    
    17
    + *
    
    18
    + * This is quite incredible, given that poll and select should be relatively
    
    19
    + * thin interfaces to the the same underlying kernel infrastructure.
    
    20
    + * Furthermore, OSX is supposedly certified as POSIX compliant! Due to this
    
    21
    + * (incompetence) we need a new I/O manager implementation based on the
    
    22
    + * antique select() API, with all of its known limitations.
    
    23
    + *
    
    24
    + * Please direct all complaints to:
    
    25
    + *   Apple Inc., One Apple Park Way, Cupertino, CA 95014, USA.
    
    26
    + *
    
    27
    + * Prototypes for functions in SelectBis.c
    
    28
    + *
    
    29
    + * -------------------------------------------------------------------------*/
    
    30
    +
    
    31
    +#pragma once
    
    32
    +
    
    33
    +#include "IOManager.h"
    
    34
    +
    
    35
    +#include "BeginPrivate.h"
    
    36
    +
    
    37
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    38
    +
    
    39
    +void initCapabilityIOManagerSelectBis(CapIOManager *iomgr);
    
    40
    +void freeCapabilityIOManagerSelectBis(CapIOManager *iomgr);
    
    41
    +
    
    42
    +/* Synchronous I/O and timer operations */
    
    43
    +bool syncIOWaitReadySelectBis(CapIOManager *iomgr, StgTSO *tso,
    
    44
    +                              IOReadOrWrite rw, HsInt fd);
    
    45
    +void syncIOCancelSelectBis(CapIOManager *iomgr, StgTSO *tso);
    
    46
    +
    
    47
    +/* Asynchronous operations */
    
    48
    +bool asyncIOWaitReadySelectBis(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    49
    +                               IOReadOrWrite rw, int fd);
    
    50
    +void asyncIOCancelSelectBis(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    51
    +
    
    52
    +/* Scheduler operations */
    
    53
    +bool anyPendingTimeoutsOrIOSelectBis(CapIOManager *iomgr);
    
    54
    +void pollCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr);
    
    55
    +bool awaitCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr);
    
    56
    +void interruptIOManagerSelectBis(CapIOManager *iomgr);
    
    57
    +
    
    58
    +#endif /* IOMGR_ENABLED_SELECTBIS */
    
    59
    +
    
    60
    +#include "EndPrivate.h"
    
    61
    +

  • rts/posix/Timeout.c
    ... ... @@ -14,8 +14,9 @@
    14 14
     #include "Schedule.h"
    
    15 15
     #include "Prelude.h"
    
    16 16
     
    
    17
    -#include "Timeout.h"
    
    17
    +#include "IOManager.h"
    
    18 18
     #include "IOManagerInternals.h"
    
    19
    +#include "Timeout.h"
    
    19 20
     #include "TimeoutQueue.h"
    
    20 21
     
    
    21 22
     #include <limits.h>
    
    ... ... @@ -24,7 +25,7 @@
    24 25
     /* Currently only used by the poll I/O manager, but in future may be used by
    
    25 26
        several in-RTS I/O managers.
    
    26 27
      */
    
    27
    -#if defined(IOMGR_ENABLED_POLL)
    
    28
    +#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_SELECTBIS)
    
    28 29
     
    
    29 30
     bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
    
    30 31
     {
    
    ... ... @@ -223,5 +224,58 @@ struct timespec *timeoutInNanoseconds(CapIOManager *iomgr, bool wait,
    223 224
     }
    
    224 225
     #endif
    
    225 226
     
    
    226
    -#endif // defined(IOMGR_ENABLED_POLL)
    
    227
    +/* select() expect a timeout in microseconds, using struct timeval * with
    
    228
    + * special values of NULL for indefinite wait, and 0 for no waiting.
    
    229
    + */
    
    230
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    231
    +struct timeval *timeoutInMicroseconds(CapIOManager *iomgr, bool wait,
    
    232
    +                                      Time now, struct timeval *tv)
    
    233
    +{
    
    234
    +    if (!wait) {
    
    235
    +        /* Don't wait, just poll. */
    
    236
    +        *tv = (struct timeval) { .tv_sec = 0, .tv_usec = 0 };
    
    237
    +        return tv;
    
    238
    +
    
    239
    +    } else if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
    
    240
    +        /* SUSv2 allows implementations to have an implementation defined
    
    241
    +         * maximum timeout for select(2). The standard requires
    
    242
    +         * implementations to silently truncate values exceeding this maximum
    
    243
    +         * to the maximum. Unfortunately, OSX and the BSD don't comply with
    
    244
    +         * SUSv2, instead opting to return EINVAL for values exceeding a
    
    245
    +         * timeout of 1e8.
    
    246
    +         *
    
    247
    +         * Select returning an error crashes the runtime in a bad way. To
    
    248
    +         * play it safe we truncate any timeout to 31 days, as SUSv2 requires
    
    249
    +         * any implementations maximum timeout to be larger than this.
    
    250
    +         *
    
    251
    +         * Truncating the timeout is not an issue, because if nothing
    
    252
    +         * interesting happens when the timeout expires, we'll see that the
    
    253
    +         * thread still wants to be blocked longer and simply block on a new
    
    254
    +         * iteration of select(2).
    
    255
    +         */
    
    256
    +        const time_t max_seconds = 2678400; // 31 * 24 * 60 * 60
    
    257
    +
    
    258
    +        Time waketime = findMinWaketimeTimeoutQueue(iomgr->timeout_queue);
    
    259
    +        Time waittime = waketime - now;
    
    260
    +
    
    261
    +        /* Any expired timeouts should have been cleared, so we must be waiting
    
    262
    +         * for a timeout in the future. */
    
    263
    +        ASSERT(waittime > 0);
    
    264
    +
    
    265
    +        tv->tv_sec  = TimeToSeconds(waittime);
    
    266
    +        if (tv->tv_sec < max_seconds) {
    
    267
    +            tv->tv_usec = TimeToUS(waittime) % 1000000;
    
    268
    +        } else {
    
    269
    +            tv->tv_sec  = max_seconds;
    
    270
    +            tv->tv_usec = 0;
    
    271
    +        }
    
    272
    +        return tv;
    
    273
    +
    
    274
    +    } else {
    
    275
    +        return NULL;
    
    276
    +    }
    
    277
    +}
    
    278
    +#endif
    
    279
    +
    
    280
    +#endif // defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_SELECTBIS)
    
    227 281
     

  • rts/posix/Timeout.h
    ... ... @@ -46,5 +46,15 @@ struct timespec *timeoutInNanoseconds(CapIOManager *iomgr, bool wait,
    46 46
                                           Time now, struct timespec *tv);
    
    47 47
     #endif
    
    48 48
     
    
    49
    +/* As above, but a timeout in microseconds. This is intended to be used with
    
    50
    + * select() which expect struct timespec *, with special values of NULL for
    
    51
    + * indefinite wait, and 0 for no waiting.
    
    52
    + */
    
    53
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    54
    +struct timeval *timeoutInMicroseconds(CapIOManager *iomgr, bool wait,
    
    55
    +                                      Time now, struct timeval *tv);
    
    56
    +
    
    57
    +#endif
    
    58
    +
    
    49 59
     #include "EndPrivate.h"
    
    50 60
     

  • rts/rts.cabal
    ... ... @@ -571,6 +571,7 @@ library
    571 571
                        wasm/JSFFI.c
    
    572 572
                        wasm/JSFFIGlobals.c
    
    573 573
                        posix/Select.c
    
    574
    +                   posix/SelectBis.c
    
    574 575
                        posix/Poll.c
    
    575 576
                        posix/Timeout.c
    
    576 577
             cmm-sources: wasm/jsval.cmm
    
    ... ... @@ -586,6 +587,7 @@ library
    586 587
                         posix/MIO.c
    
    587 588
                         posix/Poll.c
    
    588 589
                         posix/Select.c
    
    590
    +                    posix/SelectBis.c
    
    589 591
                         posix/Signals.c
    
    590 592
                         posix/Timeout.c
    
    591 593
                         posix/TTY.c

  • testsuite/tests/interface-stability/ghc-experimental-exports.stdout
    No preview for this file type