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

Commits:

20 changed files:

Changes:

  • changelog.d/select-io-manager
    1
    +section: rts
    
    2
    +issues: #27519
    
    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
    ... ... @@ -393,10 +393,11 @@ internal_to_base_MiscFlags i@Internal.MiscFlags{..} =
    393 393
         internal_to_base_ioManager Internal.IoManagerFlagAuto        = IoManagerFlagAuto
    
    394 394
         internal_to_base_ioManager Internal.IoManagerFlagSelect      = IoManagerFlagSelect
    
    395 395
     #if __GLASGOW_HASKELL__ >= 1000
    
    396
    +    internal_to_base_ioManager Internal.IoManagerFlagSelectBis   = IoManagerFlagAuto
    
    396 397
         internal_to_base_ioManager Internal.IoManagerFlagPoll        = IoManagerFlagAuto
    
    397
    -      -- This is a lie, we cannot translate poll. We cannot translate
    
    398
    -      -- accurately because want to freeze the API of the the compat RTS flags
    
    399
    -      -- here. Using "auto" is the least bad translation.
    
    398
    +      -- This is a lie, we cannot translate these new I/O managers. We cannot
    
    399
    +      -- translate accurately because want to freeze the API of the the compat
    
    400
    +      -- RTS flags here. Using "auto" is the least bad translation.
    
    400 401
           -- https://github.com/haskell/core-libraries-committee/issues/362
    
    401 402
     #endif
    
    402 403
         internal_to_base_ioManager Internal.IoManagerFlagMIO         = IoManagerFlagMIO
    

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

  • rts/IOManager.c
    ... ... @@ -33,6 +33,11 @@
    33 33
     #include "posix/Signals.h"
    
    34 34
     #endif
    
    35 35
     
    
    36
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    37
    +#include "posix/SelectBis.h"
    
    38
    +#include "posix/Timeout.h"
    
    39
    +#endif
    
    40
    +
    
    36 41
     #if defined(IOMGR_ENABLED_POLL)
    
    37 42
     #include "posix/Poll.h"
    
    38 43
     #include "posix/Timeout.h"
    
    ... ... @@ -117,6 +122,14 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
    117 122
             return IOManagerAvailable;
    
    118 123
     #else
    
    119 124
             return IOManagerUnavailable;
    
    125
    +#endif
    
    126
    +    }
    
    127
    +    else if (strcmp("selectbis", iomgrstr) == 0) {
    
    128
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    129
    +        *flag = IO_MNGR_FLAG_SELECTBIS;
    
    130
    +        return IOManagerAvailable;
    
    131
    +#else
    
    132
    +        return IOManagerUnavailable;
    
    120 133
     #endif
    
    121 134
         }
    
    122 135
         else if (strcmp("poll", iomgrstr) == 0) {
    
    ... ... @@ -226,6 +239,8 @@ void selectIOManager(void)
    226 239
     #else // !defined(THREADED_RTS)
    
    227 240
     #if   defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
    
    228 241
                 iomgr_type = IO_MANAGER_SELECT;
    
    242
    +#elif defined(IOMGR_DEFAULT_NON_THREADED_SELECTBIS)
    
    243
    +            iomgr_type = IO_MANAGER_SELECTBIS;
    
    229 244
     #elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
    
    230 245
                 iomgr_type = IO_MANAGER_POLL;
    
    231 246
     #elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
    
    ... ... @@ -244,6 +259,12 @@ void selectIOManager(void)
    244 259
                 break;
    
    245 260
     #endif
    
    246 261
     
    
    262
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    263
    +        case IO_MNGR_FLAG_SELECTBIS:
    
    264
    +            iomgr_type = IO_MANAGER_SELECTBIS;
    
    265
    +            break;
    
    266
    +#endif
    
    267
    +
    
    247 268
     #if defined(IOMGR_ENABLED_POLL)
    
    248 269
             case IO_MNGR_FLAG_POLL:
    
    249 270
                 iomgr_type = IO_MANAGER_POLL;
    
    ... ... @@ -291,6 +312,10 @@ char * showIOManager(void)
    291 312
             case IO_MANAGER_SELECT:
    
    292 313
                 return "select";
    
    293 314
     #endif
    
    315
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    316
    +        case IO_MANAGER_SELECTBIS:
    
    317
    +            return "selectbis";
    
    318
    +#endif
    
    294 319
     #if defined(IOMGR_ENABLED_POLL)
    
    295 320
             case IO_MANAGER_POLL:
    
    296 321
                 return "poll";
    
    ... ... @@ -347,6 +372,12 @@ void initCapabilityIOManager(CapIOManager *iomgr)
    347 372
                 break;
    
    348 373
     #endif
    
    349 374
     
    
    375
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    376
    +        case IO_MANAGER_SELECTBIS:
    
    377
    +            initCapabilityIOManagerSelectBis(iomgr);
    
    378
    +            break;
    
    379
    +#endif
    
    380
    +
    
    350 381
     #if defined(IOMGR_ENABLED_POLL)
    
    351 382
             case IO_MANAGER_POLL:
    
    352 383
                 initCapabilityIOManagerPoll(iomgr);
    
    ... ... @@ -380,6 +411,12 @@ void freeCapabilityIOManager(CapIOManager *iomgr)
    380 411
                 break;
    
    381 412
     #endif
    
    382 413
     
    
    414
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    415
    +        case IO_MANAGER_SELECTBIS:
    
    416
    +            freeCapabilityIOManagerSelectBis(iomgr);
    
    417
    +            break;
    
    418
    +#endif
    
    419
    +
    
    383 420
     #if defined(IOMGR_ENABLED_POLL)
    
    384 421
             case IO_MANAGER_POLL:
    
    385 422
                 freeCapabilityIOManagerPoll(iomgr);
    
    ... ... @@ -464,6 +501,7 @@ restartIOManager(CapIOManager *iomgr, Capability **pcap)
    464 501
                 break;
    
    465 502
     #endif
    
    466 503
             /* The IO_MANAGER_SELECT needs no initialisation */
    
    504
    +        /* The IO_MANAGER_SELECTBIS needs no initialisation */
    
    467 505
             /* The IO_MANAGER_POLL needs no initialisation */
    
    468 506
     
    
    469 507
             /* No impl for any of the Windows I/O managers, since no forking. */
    
    ... ... @@ -555,8 +593,14 @@ void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr)
    555 593
                 break;
    
    556 594
     #endif
    
    557 595
     
    
    596
    +#if defined(IOMGR_ENABLED_SELECTBIS) \
    
    597
    + || defined(IOMGR_ENABLED_POLL)
    
    598
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    599
    +        case IO_MANAGER_SELECTBIS:
    
    600
    +#endif
    
    558 601
     #if defined(IOMGR_ENABLED_POLL)
    
    559 602
             case IO_MANAGER_POLL:
    
    603
    +#endif
    
    560 604
                 markClosureTable(evac, user, &iomgr->aiop_table);
    
    561 605
                 evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
    
    562 606
                 break;
    
    ... ... @@ -600,6 +644,11 @@ bool anyPendingTimeoutsOrIO(CapIOManager *iomgr)
    600 644
                     || (iomgr->sleeping_queue   != END_TSO_QUEUE);
    
    601 645
     #endif
    
    602 646
     
    
    647
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    648
    +        case IO_MANAGER_SELECTBIS:
    
    649
    +            return anyPendingTimeoutsOrIOSelectBis(iomgr);
    
    650
    +#endif
    
    651
    +
    
    603 652
     #if defined(IOMGR_ENABLED_POLL)
    
    604 653
             case IO_MANAGER_POLL:
    
    605 654
                 return anyPendingTimeoutsOrIOPoll(iomgr);
    
    ... ... @@ -659,6 +708,12 @@ void pollCompletedTimeoutsOrIO(CapIOManager *iomgr)
    659 708
               break;
    
    660 709
     #endif
    
    661 710
     
    
    711
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    712
    +        case IO_MANAGER_SELECTBIS:
    
    713
    +          pollCompletedTimeoutsOrIOSelectBis(iomgr);
    
    714
    +          break;
    
    715
    +#endif
    
    716
    +
    
    662 717
     #if defined(IOMGR_ENABLED_POLL)
    
    663 718
             case IO_MANAGER_POLL:
    
    664 719
               pollCompletedTimeoutsOrIOPoll(iomgr);
    
    ... ... @@ -685,6 +740,7 @@ void pollCompletedTimeoutsOrIO(CapIOManager *iomgr)
    685 740
     bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr)
    
    686 741
     {
    
    687 742
         debugTrace(DEBUG_iomanager, "waiting for completed IO or timeouts");
    
    743
    +    ASSERT(emptyRunQueue(iomgr->cap));
    
    688 744
         bool completed = true; // wait completed or interrupted?
    
    689 745
         switch (iomgr_type) {
    
    690 746
     #if defined(IOMGR_ENABLED_SELECT)
    
    ... ... @@ -693,6 +749,12 @@ bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr)
    693 749
               break;
    
    694 750
     #endif
    
    695 751
     
    
    752
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    753
    +        case IO_MANAGER_SELECTBIS:
    
    754
    +          completed = awaitCompletedTimeoutsOrIOSelectBis(iomgr);
    
    755
    +          break;
    
    756
    +#endif
    
    757
    +
    
    696 758
     #if defined(IOMGR_ENABLED_POLL)
    
    697 759
             case IO_MANAGER_POLL:
    
    698 760
               completed = awaitCompletedTimeoutsOrIOPoll(iomgr);
    
    ... ... @@ -734,6 +796,12 @@ void interruptIOManager(CapIOManager *iomgr)
    734 796
                 break;
    
    735 797
     #endif
    
    736 798
     
    
    799
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    800
    +        case IO_MANAGER_SELECTBIS:
    
    801
    +            interruptIOManagerSelectBis(iomgr);
    
    802
    +            break;
    
    803
    +#endif
    
    804
    +
    
    737 805
     #if defined(IOMGR_ENABLED_POLL)
    
    738 806
             case IO_MANAGER_POLL:
    
    739 807
                 interruptIOManagerPoll(iomgr);
    
    ... ... @@ -761,10 +829,10 @@ void interruptIOManager(CapIOManager *iomgr)
    761 829
     
    
    762 830
     
    
    763 831
     /* CMM primop. Result is true on success, or false on allocation failure. */
    
    764
    -IOSubmitResult syncIOWaitReady(CapIOManager *iomgr,
    
    765
    -                               StgTSO       *tso,
    
    766
    -                               IOReadOrWrite rw,
    
    767
    -                               HsInt         fd)
    
    832
    +IOSubmitResult syncIOWaitReady(CapIOManager      *iomgr,
    
    833
    +                               StgTSO            *tso,
    
    834
    +                               enum IOReadOrWrite rw,
    
    835
    +                               HsInt              fd)
    
    768 836
     {
    
    769 837
         debugTrace(DEBUG_iomanager,
    
    770 838
                    "thread %ld waiting for %s I/O readiness on fd %d",
    
    ... ... @@ -783,6 +851,10 @@ IOSubmitResult syncIOWaitReady(CapIOManager *iomgr,
    783 851
                 return IOSubmitResultAsyncContinue;
    
    784 852
             }
    
    785 853
     #endif
    
    854
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    855
    +        case IO_MANAGER_SELECTBIS:
    
    856
    +            return syncIOWaitReadySelectBis(iomgr, tso, rw, fd);
    
    857
    +#endif
    
    786 858
     #if defined(IOMGR_ENABLED_POLL)
    
    787 859
             case IO_MANAGER_POLL:
    
    788 860
                 return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
    
    ... ... @@ -803,6 +875,13 @@ void syncIOCancel(CapIOManager *iomgr, StgTSO *tso)
    803 875
                                         &iomgr->blocked_queue_hd,
    
    804 876
                                         &iomgr->blocked_queue_tl,
    
    805 877
                                         tso);
    
    878
    +            appendToRunQueue(iomgr->cap, tso);
    
    879
    +            RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    880
    +            break;
    
    881
    +#endif
    
    882
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    883
    +        case IO_MANAGER_SELECTBIS:
    
    884
    +            syncIOCancelSelectBis(iomgr, tso);
    
    806 885
                 break;
    
    807 886
     #endif
    
    808 887
     #if defined(IOMGR_ENABLED_POLL)
    
    ... ... @@ -817,11 +896,14 @@ void syncIOCancel(CapIOManager *iomgr, StgTSO *tso)
    817 896
                                         &iomgr->blocked_queue_tl,
    
    818 897
                                         tso);
    
    819 898
                 abandonWorkRequest(tso->block_info.async_reqID);
    
    899
    +            appendToRunQueue(iomgr->cap, tso);
    
    900
    +            RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    820 901
                 break;
    
    821 902
     #endif
    
    822 903
             default:
    
    823 904
                 barf("syncIOCancel not supported for I/O manager %d", iomgr_type);
    
    824 905
         }
    
    906
    +    ASSERT(tso->why_blocked == NotBlocked);
    
    825 907
     }
    
    826 908
     
    
    827 909
     
    
    ... ... @@ -848,8 +930,14 @@ bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
    848 930
                 return true;
    
    849 931
             }
    
    850 932
     #endif
    
    933
    +#if defined(IOMGR_ENABLED_SELECTBIS) \
    
    934
    + || defined(IOMGR_ENABLED_POLL)
    
    935
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    936
    +        case IO_MANAGER_SELECTBIS:
    
    937
    +#endif
    
    851 938
     #if defined(IOMGR_ENABLED_POLL)
    
    852 939
             case IO_MANAGER_POLL:
    
    940
    +#endif
    
    853 941
                 return syncDelayTimeout(iomgr, tso, us_delay);
    
    854 942
     #endif
    
    855 943
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -883,10 +971,18 @@ void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso)
    883 971
             case IO_MANAGER_SELECT:
    
    884 972
                 ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
    
    885 973
                 removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
    
    974
    +            appendToRunQueue(iomgr->cap, tso);
    
    975
    +            RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    886 976
                 break;
    
    887 977
     #endif
    
    978
    +#if defined(IOMGR_ENABLED_SELECTBIS) \
    
    979
    + || defined(IOMGR_ENABLED_POLL)
    
    980
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    981
    +        case IO_MANAGER_SELECTBIS:
    
    982
    +#endif
    
    888 983
     #if defined(IOMGR_ENABLED_POLL)
    
    889 984
             case IO_MANAGER_POLL:
    
    985
    +#endif
    
    890 986
                 syncDelayCancelTimeout(iomgr, tso);
    
    891 987
                 break;
    
    892 988
     #endif
    
    ... ... @@ -900,6 +996,7 @@ void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso)
    900 996
             default:
    
    901 997
                 barf("syncDelayCancel not supported for I/O manager %d", iomgr_type);
    
    902 998
         }
    
    999
    +    ASSERT(tso->why_blocked == NotBlocked);
    
    903 1000
     }
    
    904 1001
     
    
    905 1002
     
    

  • 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
    
    ... ... @@ -212,7 +226,19 @@ char * showIOManager(void);
    212 226
      */
    
    213 227
     bool is_io_mng_native_p (void);
    
    214 228
     
    
    229
    +/* Values for StgAsyncIOOp.operation.
    
    230
    + *
    
    231
    + * Note: this is encoded in 6 bits in StgAsyncIOOp.
    
    232
    + */
    
    233
    +enum IOOpCode {
    
    234
    +    IOOpCodeWaitRead  = 0,
    
    235
    +    IOOpCodeWaitWrite = 1
    
    236
    +    /* This will be extended, e.g. for Read/Write */
    
    237
    +};
    
    238
    +
    
    215 239
     /* Values for StgAsyncIOOp.outcome.
    
    240
    + *
    
    241
    + * Note: this is encoded in 2 bits in StgAsyncIOOp.
    
    216 242
      */
    
    217 243
     enum IOOpOutcome {
    
    218 244
         IOOpOutcomeInFlight  = 0,
    
    ... ... @@ -314,7 +340,17 @@ void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr);
    314 340
     /* Several code paths are almost identical between read and write paths. In
    
    315 341
      * such cases we use a shared code path with an enum to say which we're doing.
    
    316 342
      */
    
    317
    -typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite;
    
    343
    +enum IOReadOrWrite { IORead = 0, IOWrite = 1 };
    
    344
    +
    
    345
    +INLINE_HEADER enum IOOpCode convIOReadOrWriteToIOOpCode (enum IOReadOrWrite rw)
    
    346
    +{
    
    347
    +    // The codes are compatible:
    
    348
    +    ASSERT((int) IOOpCodeWaitRead  == (int) IORead &&
    
    349
    +           (int) IOOpCodeWaitWrite == (int) IOWrite);
    
    350
    +
    
    351
    +    return (enum IOOpCode) rw;
    
    352
    +}
    
    353
    +
    
    318 354
     
    
    319 355
     /* Synchronous operations: I/O and delays. As synchronous operations they
    
    320 356
      * necessarily operate on threads. The thread is suspended until the operation
    
    ... ... @@ -351,13 +387,16 @@ enum IOSubmitResultCodes {
    351 387
     };
    
    352 388
     
    
    353 389
     /* Called from CMM primop */
    
    354
    -IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
    
    390
    +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso,
    
    391
    +                               enum IOReadOrWrite rw, HsInt fd);
    
    355 392
     
    
    393
    +/* Cancel the I/O the TSO is blocked on and add the TSO to the run queue */
    
    356 394
     void syncIOCancel(CapIOManager *iomgr, StgTSO *tso);
    
    357 395
     
    
    358 396
     /* Called from CMM primop */
    
    359 397
     bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay);
    
    360 398
     
    
    399
    +/* Cancel the timeout the TSO is blocked on and add the TSO to the run queue */
    
    361 400
     void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso);
    
    362 401
     
    
    363 402
     #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -390,6 +429,11 @@ void pollCompletedTimeoutsOrIO(CapIOManager *iomgr);
    390 429
      * does complete (or we get a signal with a handler) and process the
    
    391 430
      * completions as appropriate.
    
    392 431
      *
    
    432
    + * This should _only_ be called when there are no runnable threads and it is
    
    433
    + * thus accepable to block and wait for I/O or timeouts. Notably this means it
    
    434
    + * must _not_ be used in the threaded RTS (where it is unacceptable to block
    
    435
    + * a capability).
    
    436
    + *
    
    393 437
      * Upon returning true this guarantees that the scheduler run queue is
    
    394 438
      * non-empty or that the scheduler is no longer in the running state.
    
    395 439
      * Succinctly, the post-condition in the return true case is
    

  • rts/IOManagerInternals.h
    ... ... @@ -14,12 +14,20 @@
    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) \
    
    18
    + || defined(IOMGR_ENABLED_POLL)
    
    19 19
     #include "ClosureTable.h"
    
    20 20
     #include "TimeoutQueue.h"
    
    21 21
     #endif
    
    22 22
     
    
    23
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    24
    +#include <sys/select.h> /* for fd_set */
    
    25
    +#endif
    
    26
    +
    
    27
    +#if defined(IOMGR_ENABLED_POLL)
    
    28
    +#include <poll.h> /* for struct pollfd */
    
    29
    +#endif
    
    30
    +
    
    23 31
     #include "BeginPrivate.h"
    
    24 32
     
    
    25 33
     /* The per-capability data structures belonging to the I/O manager.
    
    ... ... @@ -46,19 +54,26 @@ struct _CapIOManager {
    46 54
         StgTSO *sleeping_queue;
    
    47 55
     #endif
    
    48 56
     
    
    49
    -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
    
    57
    +#if defined(IOMGR_ENABLED_SELECT) \
    
    58
    + || defined(IOMGR_ENABLED_SELECTBIS) \
    
    59
    + || defined(IOMGR_ENABLED_POLL)
    
    50 60
     #if defined(HAVE_PREEMPTION)
    
    51 61
         /* FDs for waking up the I/O manager when it is blocked waiting */
    
    52 62
         int interrupt_fd_r, interrupt_fd_w;
    
    53 63
     #endif
    
    54 64
     #endif
    
    55 65
     
    
    56
    -#if defined(IOMGR_ENABLED_POLL)
    
    66
    +#if defined(IOMGR_ENABLED_POLL) \
    
    67
    + || defined(IOMGR_ENABLED_SELECTBIS)
    
    57 68
         /* AIOP and timeout collections shared by several I/O manager impls */
    
    58 69
         ClosureTable     aiop_table;
    
    59 70
         StgTimeoutQueue *timeout_queue;
    
    60 71
     #endif
    
    61 72
     
    
    73
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    74
    +    fd_set *rfds, *wfds;
    
    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/RaiseAsync.c
    ... ... @@ -28,7 +28,7 @@
    28 28
     static void blockedThrowTo (Capability *cap,
    
    29 29
                                 StgTSO *target, MessageThrowTo *msg);
    
    30 30
     
    
    31
    -static void removeFromQueues(Capability *cap, StgTSO *tso);
    
    31
    +static void unblockAndAppendToRunQueue(Capability *cap, StgTSO *tso);
    
    32 32
     
    
    33 33
     static void removeFromMVarBlockedQueue (StgTSO *tso);
    
    34 34
     
    
    ... ... @@ -62,8 +62,10 @@ throwToSingleThreaded__ (Capability *cap, StgTSO *tso, StgClosure *exception,
    62 62
             return;
    
    63 63
         }
    
    64 64
     
    
    65
    -    // Remove it from any blocking queues
    
    66
    -    removeFromQueues(cap,tso);
    
    65
    +    // Remove it from any blocking queues and add it to the run queue
    
    66
    +    unblockAndAppendToRunQueue(cap,tso);
    
    67
    +    ASSERT(tso->why_blocked == NotBlocked ||
    
    68
    +           tso->why_blocked == ThreadMigrating);
    
    67 69
     
    
    68 70
         raiseAsync(cap, tso, exception, stop_at_atomically, stop_here);
    
    69 71
     }
    
    ... ... @@ -471,7 +473,7 @@ check_target:
    471 473
                 blockedThrowTo(cap,target,msg);
    
    472 474
                 return THROWTO_BLOCKED;
    
    473 475
             } else {
    
    474
    -            removeFromQueues(cap,target);
    
    476
    +            unblockAndAppendToRunQueue(cap,target);
    
    475 477
                 raiseAsync(cap, target, msg->exception, false, NULL);
    
    476 478
                 return THROWTO_SUCCESS;
    
    477 479
             }
    
    ... ... @@ -612,16 +614,7 @@ awakenBlockedExceptionQueue (Capability *cap, StgTSO *tso)
    612 614
         tso->blocked_exceptions = END_BLOCKED_EXCEPTIONS_QUEUE;
    
    613 615
     }
    
    614 616
     
    
    615
    -/* -----------------------------------------------------------------------------
    
    616
    -   Remove a thread from blocking queues.
    
    617
    -
    
    618
    -   This is for use when we raise an exception in another thread, which
    
    619
    -   may be blocked.
    
    620
    -
    
    621
    -   Precondition: we have exclusive access to the TSO, via the same set
    
    622
    -   of conditions as throwToSingleThreaded() (c.f.).
    
    623
    -   -------------------------------------------------------------------------- */
    
    624
    -
    
    617
    +// Helper for unblockAndAppendToRunQueue
    
    625 618
     static void
    
    626 619
     removeFromMVarBlockedQueue (StgTSO *tso)
    
    627 620
     {
    
    ... ... @@ -664,13 +657,24 @@ removeFromMVarBlockedQueue (StgTSO *tso)
    664 657
         tso->_link = END_TSO_QUEUE;
    
    665 658
     }
    
    666 659
     
    
    660
    +/* -----------------------------------------------------------------------------
    
    661
    +   Remove a thread from blocking queues (if any) and add it to the run queue
    
    662
    +   (if it wasn't on the run queue already).
    
    663
    +
    
    664
    +   This is for use when we raise an exception in another thread, which
    
    665
    +   may be blocked.
    
    666
    +
    
    667
    +   Precondition: we have exclusive access to the TSO, via the same set
    
    668
    +   of conditions as throwToSingleThreaded() (c.f.).
    
    669
    +   -------------------------------------------------------------------------- */
    
    670
    +
    
    667 671
     static void
    
    668
    -removeFromQueues(Capability *cap, StgTSO *tso)
    
    672
    +unblockAndAppendToRunQueue(Capability *cap, StgTSO *tso)
    
    669 673
     {
    
    670 674
       switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
    
    671 675
     
    
    672
    -  case NotBlocked:
    
    673
    -  case ThreadMigrating:
    
    676
    +  case NotBlocked:      // Already on the run queue
    
    677
    +  case ThreadMigrating: // Not added to the run queue
    
    674 678
           return;
    
    675 679
     
    
    676 680
       case BlockedOnSTM:
    
    ... ... @@ -680,16 +684,16 @@ removeFromQueues(Capability *cap, StgTSO *tso)
    680 684
         // perhaps have a debugging test to make sure that this really
    
    681 685
         // happens and that the 'zombie' transaction does not get
    
    682 686
         // committed.
    
    683
    -    goto done;
    
    687
    +    break;
    
    684 688
     
    
    685 689
       case BlockedOnMVar:
    
    686 690
       case BlockedOnMVarRead:
    
    687 691
           removeFromMVarBlockedQueue(tso);
    
    688
    -      goto done;
    
    692
    +      break;
    
    689 693
     
    
    690 694
       case BlockedOnBlackHole:
    
    691 695
           // nothing to do
    
    692
    -      goto done;
    
    696
    +      break;
    
    693 697
     
    
    694 698
       case BlockedOnMsgThrowTo:
    
    695 699
       {
    
    ... ... @@ -709,18 +713,19 @@ removeFromQueues(Capability *cap, StgTSO *tso)
    709 713
       case BlockedOnDoProc:
    
    710 714
           // These blocking reasons are only used by some I/O managers
    
    711 715
           syncIOCancel(cap->iomgr, tso);
    
    712
    -      goto done;
    
    716
    +      return;
    
    713 717
     
    
    714 718
       case BlockedOnDelay:
    
    715 719
           // This blocking reasons is only used by some I/O managers
    
    716 720
           syncDelayCancel(cap->iomgr, tso);
    
    717
    -      goto done;
    
    721
    +      return;
    
    718 722
     
    
    719 723
       default:
    
    720
    -      barf("removeFromQueues: %d", tso->why_blocked);
    
    724
    +      barf("unblockAndAppendToRunQueue: %d", tso->why_blocked);
    
    721 725
       }
    
    722 726
     
    
    723
    - done:
    
    727
    +  // The cases above that use return add the TSO to the run queue themselves
    
    728
    +  // (or don't need to). For the rest (that use break) we do it here.
    
    724 729
       appendToRunQueue(cap, tso);
    
    725 730
       RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    726 731
     }
    

  • 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/include/rts/storage/Closures.h
    ... ... @@ -823,6 +823,8 @@ typedef struct {
    823 823
           // In the threaded way there is one I/O manager per capability. We have
    
    824 824
           // to handle cross-capability I/O op cancellation specially, so we need
    
    825 825
           // to know which capability an aiop is being managed on.
    
    826
    +      //
    
    827
    +      // We could probably afford to steal some bits here if needed.
    
    826 828
         uint16_t capno;
    
    827 829
     
    
    828 830
           // This tells us which thing the notify union above contains. It is a
    
    ... ... @@ -837,7 +839,30 @@ typedef struct {
    837 839
           // 3: IOOpOutcomeCancelled: cancelled, no further detail.
    
    838 840
         uint16_t outcome: 2;
    
    839 841
     
    
    840
    -      // 12 bits going spare!
    
    842
    +      // The I/O operation we are performing. It is a value from enum IOOpCode,
    
    843
    +      // but we don't use the enum type here due to portability concerns for
    
    844
    +      // this C bitfield.
    
    845
    +      //
    
    846
    +      // The size of this field, allows us up to 64 opcodes.
    
    847
    +    uint16_t operation: 6;
    
    848
    +
    
    849
    +      // 6 bits going spare!
    
    850
    +    uint16_t padding: 6;
    
    851
    +
    
    852
    +      // The file descriptor the operation is on. This is used in several I/O
    
    853
    +      // managers to group StgAsyncIOOps by fd. In particular this is needed
    
    854
    +      // for cancelling all wait-notification ops when closing an fd. It is
    
    855
    +      // also handy for logging and debugging.
    
    856
    +      //
    
    857
    +      // Note that it is technically possible to stuff Win32 HANDLEs into here,
    
    858
    +      // but no Win32 I/O manager does this _yet_. See:
    
    859
    +      // https://learn.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
    
    860
    +      // > 64-bit versions of Windows use 32-bit handles for interoperability.
    
    861
    +      // > When sharing a handle between 32-bit and 64-bit applications, only
    
    862
    +      // > the lower 32 bits are significant, so it is safe to truncate the
    
    863
    +      // > handle (when passing it from 64-bit to 32-bit) or sign-extend the
    
    864
    +      // > handle (when passing it from 32-bit to 64-bit).
    
    865
    +    uint32_t fd;
    
    841 866
     
    
    842 867
         union {
    
    843 868
               // For successful outcomes, this is the result code of the operation.
    
    ... ... @@ -850,10 +875,6 @@ typedef struct {
    850 875
             uint32_t error;
    
    851 876
         };
    
    852 877
     
    
    853
    -        // Round it up to 2 words on 64bit platforms.
    
    854
    -        // This is also space for future extension, without increasing the size.
    
    855
    -    uint32_t padding;
    
    856
    -
    
    857 878
           // Note that because we use fixed size Ctypes here then the size in words
    
    858 879
           // of this heap object is different on 32bit and 64bit platforms.
    
    859 880
           // We handle this in the INFO_TABLE_CONSTR decl for stg_ASYNCIOOP using
    

  • rts/posix/Poll.c
    ... ... @@ -132,7 +132,7 @@ the aiop_table, but still allows the full_poll_table to have an extra entry.
    132 132
     /* Forward declarations */
    
    133 133
     static bool enlargeTables(CapIOManager *iomgr);
    
    134 134
     static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    135
    -static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    135
    +static void removeFromTables(CapIOManager *iomgr, int i);
    
    136 136
     static void reportPollError(int res, nfds_t nfds) STG_NORETURN;
    
    137 137
     
    
    138 138
     
    
    ... ... @@ -172,7 +172,7 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr)
    172 172
     
    
    173 173
     /* Used to implement syncIOWaitReady. */
    
    174 174
     IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    
    175
    -                                   IOReadOrWrite rw, HsInt fd)
    
    175
    +                                   enum IOReadOrWrite rw, HsInt fd)
    
    176 176
     {
    
    177 177
         StgAsyncIOOp *aiop;
    
    178 178
         aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp));
    
    ... ... @@ -188,7 +188,7 @@ IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    188 188
     }
    
    189 189
     
    
    190 190
     IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    191
    -                                    IOReadOrWrite rw, int fd)
    
    191
    +                                    enum IOReadOrWrite rw, int fd)
    
    192 192
     {
    
    193 193
         if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
    
    194 194
             bool ok = enlargeTables(iomgr);
    
    ... ... @@ -203,9 +203,11 @@ IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    203 203
         /* The syncIO wrapper or CMM primop filled in the notify and live fields,
    
    204 204
          * we fill the rest.
    
    205 205
          */
    
    206
    -    aiop->capno   = iomgr->cap->no;
    
    207
    -    aiop->index   = ix;
    
    208
    -    aiop->outcome = IOOpOutcomeInFlight;
    
    206
    +    aiop->capno     = iomgr->cap->no;
    
    207
    +    aiop->index     = ix;
    
    208
    +    aiop->outcome   = IOOpOutcomeInFlight;
    
    209
    +    aiop->operation = convIOReadOrWriteToIOOpCode(rw);
    
    210
    +    aiop->fd        = fd;
    
    209 211
     
    
    210 212
         /* Fill in the corresponding entry in the aiop_poll_table */
    
    211 213
         iomgr->aiop_poll_table[ix] = (struct pollfd) {
    
    ... ... @@ -222,20 +224,7 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso)
    222 224
         StgAsyncIOOp *aiop  = tso->block_info.aiop;
    
    223 225
         ASSERT(aiop->notify_type == NotifyTSO);
    
    224 226
         ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
    
    225
    -    ioCancel(iomgr, aiop);
    
    226
    -    setTsoIOOpOutcome(tso, aiop->outcome, aiop->result);
    
    227
    -    /* We cannot use the normal notifyIOCompletion here. We are in the context
    
    228
    -     * of throwTo, interrupting a thread blocked on IO via an async exception.
    
    229
    -     * We don't put the TSO back on the run queue or change the why_blocked
    
    230
    -     * status, as that is done by removeFromQueues (in the throwTo* functions).
    
    231
    -     */
    
    232
    -
    
    233
    -    /* We are in the TSO case, where the aiop was only reachable from the TSO
    
    234
    -     * itself, and thus it is now no longer be reachable at all.
    
    235
    -     */
    
    236
    -    IF_NONMOVING_WRITE_BARRIER_ENABLED {
    
    237
    -        updateRemembSetPushClosure(iomgr->cap, (StgClosure *)aiop);
    
    238
    -    }
    
    227
    +    asyncIOCancelPoll(iomgr, aiop);
    
    239 228
     }
    
    240 229
     
    
    241 230
     
    
    ... ... @@ -246,29 +235,14 @@ void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    246 235
          * because each aiop is GC heap allocated, so cannot be recycled until it
    
    247 236
          * is no longer retained by the application.
    
    248 237
          */
    
    249
    -    ASSERT(aiop->notify_type != NotifyTSO);
    
    250 238
         if (indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop) {
    
    251
    -        ioCancel(iomgr, aiop);
    
    239
    +        removeFromTables(iomgr, aiop->index);
    
    240
    +        aiop->outcome = IOOpOutcomeCancelled;
    
    252 241
             notifyIOCompletion(iomgr, aiop);
    
    253 242
         }
    
    254 243
     }
    
    255 244
     
    
    256 245
     
    
    257
    -static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    
    258
    -{
    
    259
    -    int ix = aiop->index;
    
    260
    -    int ix_from; int ix_to;
    
    261
    -    removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, ix,
    
    262
    -                              &ix_from, &ix_to);
    
    263
    -    if (ix_to != ix_from) {
    
    264
    -        StgAsyncIOOp *aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    265
    -        aiop_to->index = ix_to;
    
    266
    -        iomgr->aiop_poll_table[ix_to] = iomgr->aiop_poll_table[ix_from];
    
    267
    -    }
    
    268
    -    aiop->outcome = IOOpOutcomeCancelled;
    
    269
    -}
    
    270
    -
    
    271
    -
    
    272 246
     bool anyPendingTimeoutsOrIOPoll(CapIOManager *iomgr)
    
    273 247
     {
    
    274 248
         return !isEmptyTimeoutQueue(iomgr->timeout_queue)
    
    ... ... @@ -313,6 +287,15 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    313 287
     }
    
    314 288
     
    
    315 289
     
    
    290
    +/* Called from poll/awaitCompletedTimeoutsOrIOPoll after a successful poll()
    
    291
    + * call to process all the I/O completions.
    
    292
    + *
    
    293
    + * We match up the I/O completion notifications from the system call against
    
    294
    + * the pending I/O operations from the aiop_table, and use notifyIOCompletion
    
    295
    + * on each completed aiop.
    
    296
    + *
    
    297
    + * Returns true if the poll() was interupted via iomgr->interrupt_fd_r.
    
    298
    + */
    
    316 299
     static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    
    317 300
     {
    
    318 301
         /* The scheme we use with poll is that we have a dense poll table, and a
    
    ... ... @@ -323,6 +306,7 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    323 306
          */
    
    324 307
         debugTrace(DEBUG_iomanager, "processIOCompletions(ncompletions = %d)",
    
    325 308
                                     ncompletions);
    
    309
    +    ASSERT(ncompletions > 0);
    
    326 310
     
    
    327 311
         bool interrupt = false;
    
    328 312
     #if defined(HAVE_PREEMPTION)
    
    ... ... @@ -337,9 +321,8 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    337 321
     #endif
    
    338 322
     
    
    339 323
         struct pollfd *aiop_poll_table = iomgr->aiop_poll_table;
    
    340
    -    int n = ncompletions;
    
    341 324
         int i = 0;
    
    342
    -    while (n > 0) {
    
    325
    +    while (ncompletions > 0) {
    
    343 326
             ASSERT(i < sizeClosureTable(&iomgr->aiop_table));
    
    344 327
     
    
    345 328
             /* Since each aiop_table entry is for a single (fd, rw) pair, we
    
    ... ... @@ -349,6 +332,8 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    349 332
             if (aiop_poll_table[i].revents) {
    
    350 333
                 StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, i);
    
    351 334
     
    
    335
    +            ASSERT(aiop->outcome == IOOpOutcomeInFlight);
    
    336
    +
    
    352 337
                 /* We do need to handle POLLNVAL, but we do not need to do anything
    
    353 338
                  * special for POLLERR or POLLHUP. (See man poll for details).
    
    354 339
                  * The calling thread will typically try to do I/O after waiting
    
    ... ... @@ -364,26 +349,14 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    364 349
                     aiop->result  = 0;
    
    365 350
                 }
    
    366 351
     
    
    367
    -            /* Remove from the completion table, preserving compactness, and
    
    368
    -             * apply the same compacting to the aiop_poll_table.
    
    369
    -             */
    
    370
    -            int ix_from; int ix_to;
    
    371
    -            removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, i,
    
    372
    -                                      &ix_from, &ix_to);
    
    373
    -            if (ix_to != ix_from) {
    
    374
    -                StgAsyncIOOp *aiop_to;
    
    375
    -                aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    376
    -                aiop_to->index = ix_to;
    
    377
    -                aiop_poll_table[ix_to] = aiop_poll_table[ix_from];
    
    378
    -            }
    
    379
    -
    
    352
    +            removeFromTables(iomgr, i);
    
    380 353
                 notifyIOCompletion(iomgr, aiop);
    
    381
    -            n--;
    
    354
    +            ncompletions--;
    
    382 355
             } else {
    
    383
    -            /* You'd expect incrementing the poll table index to be
    
    384
    -             * unconditional, but we don't increment the index if we did
    
    385
    -             * process the entry, because using removeCompactClosureTable
    
    386
    -             * means we'll move an entry from the end into the same index.
    
    356
    +            /* You'd expect incrementing the table index to be unconditional,
    
    357
    +             * but we don't increment the index if we did process the entry,
    
    358
    +             * because using removeFromTables means we'll move an entry from
    
    359
    +             * the end of the table into the index i.
    
    387 360
                  */
    
    388 361
                 i++;
    
    389 362
             }
    
    ... ... @@ -564,6 +537,7 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    564 537
         return !interrupt;
    
    565 538
     }
    
    566 539
     
    
    540
    +
    
    567 541
     static void reportPollError(int res, nfds_t nfds)
    
    568 542
     {
    
    569 543
         if (errno == EINVAL) {
    
    ... ... @@ -619,4 +593,20 @@ static bool enlargeTables(CapIOManager *iomgr)
    619 593
         return true;
    
    620 594
     }
    
    621 595
     
    
    596
    +
    
    597
    +/* Remove from the completion table, preserving compactness, and apply the same
    
    598
    + * compacting to the aiop_poll_table.
    
    599
    + */
    
    600
    +static void removeFromTables(CapIOManager *iomgr, int ix)
    
    601
    +{
    
    602
    +    int ix_from; int ix_to;
    
    603
    +    removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, ix,
    
    604
    +                              &ix_from, &ix_to);
    
    605
    +    if (ix_to != ix_from) {
    
    606
    +        StgAsyncIOOp *aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to);
    
    607
    +        aiop_to->index = ix_to;
    
    608
    +        iomgr->aiop_poll_table[ix_to] = iomgr->aiop_poll_table[ix_from];
    
    609
    +    }
    
    610
    +}
    
    611
    +
    
    622 612
     #endif /* IOMGR_ENABLED_POLL */

  • rts/posix/Poll.h
    ... ... @@ -21,12 +21,12 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr);
    21 21
     
    
    22 22
     /* Synchronous I/O and timer operations */
    
    23 23
     IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    
    24
    -                                   IOReadOrWrite rw, HsInt fd);
    
    24
    +                                   enum IOReadOrWrite rw, HsInt fd);
    
    25 25
     void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso);
    
    26 26
     
    
    27 27
     /* Asynchronous operations */
    
    28 28
     IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    29
    -                                    IOReadOrWrite rw, int fd);
    
    29
    +                                    enum IOReadOrWrite rw, int fd);
    
    30 30
     void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    31 31
     
    
    32 32
     /* Scheduler operations */
    

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

  • 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,8 @@
    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_SELECTBIS) \
    
    29
    + || defined(IOMGR_ENABLED_POLL)
    
    28 30
     
    
    29 31
     bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
    
    30 32
     {
    
    ... ... @@ -68,14 +70,13 @@ void syncDelayCancelTimeout(CapIOManager *iomgr, StgTSO *tso)
    68 70
     
    
    69 71
         deleteTimeoutQueue(&iomgr->timeout_queue, timeout);
    
    70 72
     
    
    73
    +    appendToRunQueue(iomgr->cap, tso);
    
    74
    +    RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    75
    +
    
    71 76
         /* the timeout is no longer accessible from anywhere (except here) */
    
    72 77
         IF_NONMOVING_WRITE_BARRIER_ENABLED {
    
    73 78
             updateRemembSetPushClosure(iomgr->cap, (StgClosure *)timeout);
    
    74 79
         }
    
    75
    -
    
    76
    -    /* We don't put the TSO back on the run queue or change the why_blocked
    
    77
    -       status, as that is done by removeFromQueues (in the throwTo* functions).
    
    78
    -     */
    
    79 80
     }
    
    80 81
     
    
    81 82
     static void notifyTimeoutCompletion(CapIOManager *iomgr, StgTimeout *timeout);
    
    ... ... @@ -222,5 +223,58 @@ struct timespec *timeoutInNanoseconds(CapIOManager *iomgr, bool wait,
    222 223
     }
    
    223 224
     #endif
    
    224 225
     
    
    225
    -#endif // defined(IOMGR_ENABLED_POLL)
    
    226
    +/* select() expect a timeout in microseconds, using struct timeval * with
    
    227
    + * special values of NULL for indefinite wait, and 0 for no waiting.
    
    228
    + */
    
    229
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    230
    +struct timeval *timeoutInMicroseconds(CapIOManager *iomgr, bool wait,
    
    231
    +                                      Time now, struct timeval *tv)
    
    232
    +{
    
    233
    +    if (!wait) {
    
    234
    +        /* Don't wait, just poll. */
    
    235
    +        *tv = (struct timeval) { .tv_sec = 0, .tv_usec = 0 };
    
    236
    +        return tv;
    
    237
    +
    
    238
    +    } else if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
    
    239
    +        /* SUSv2 allows implementations to have an implementation defined
    
    240
    +         * maximum timeout for select(2). The standard requires
    
    241
    +         * implementations to silently truncate values exceeding this maximum
    
    242
    +         * to the maximum. Unfortunately, OSX and the BSD don't comply with
    
    243
    +         * SUSv2, instead opting to return EINVAL for values exceeding a
    
    244
    +         * timeout of 1e8.
    
    245
    +         *
    
    246
    +         * Select returning an error crashes the runtime in a bad way. To
    
    247
    +         * play it safe we truncate any timeout to 31 days, as SUSv2 requires
    
    248
    +         * any implementations maximum timeout to be larger than this.
    
    249
    +         *
    
    250
    +         * Truncating the timeout is not an issue, because if nothing
    
    251
    +         * interesting happens when the timeout expires, we'll see that the
    
    252
    +         * thread still wants to be blocked longer and simply block on a new
    
    253
    +         * iteration of select(2).
    
    254
    +         */
    
    255
    +        const time_t max_seconds = 2678400; // 31 * 24 * 60 * 60
    
    256
    +
    
    257
    +        Time waketime = findMinWaketimeTimeoutQueue(iomgr->timeout_queue);
    
    258
    +        Time waittime = waketime - now;
    
    259
    +
    
    260
    +        /* Any expired timeouts should have been cleared, so we must be waiting
    
    261
    +         * for a timeout in the future. */
    
    262
    +        ASSERT(waittime > 0);
    
    263
    +
    
    264
    +        tv->tv_sec  = TimeToSeconds(waittime);
    
    265
    +        if (tv->tv_sec < max_seconds) {
    
    266
    +            tv->tv_usec = TimeToUS(waittime) % 1000000;
    
    267
    +        } else {
    
    268
    +            tv->tv_sec  = max_seconds;
    
    269
    +            tv->tv_usec = 0;
    
    270
    +        }
    
    271
    +        return tv;
    
    272
    +
    
    273
    +    } else {
    
    274
    +        return NULL;
    
    275
    +    }
    
    276
    +}
    
    277
    +#endif
    
    278
    +
    
    279
    +#endif // defined(IOMGR_ENABLED_POLL) || ... etc
    
    226 280
     

  • 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
    ... ... @@ -568,6 +568,7 @@ library
    568 568
                        wasm/JSFFI.c
    
    569 569
                        wasm/JSFFIGlobals.c
    
    570 570
                        posix/Select.c
    
    571
    +                   posix/SelectBis.c
    
    571 572
                        posix/Poll.c
    
    572 573
                        posix/Timeout.c
    
    573 574
             cmm-sources: wasm/jsval.cmm
    
    ... ... @@ -583,6 +584,7 @@ library
    583 584
                         posix/MIO.c
    
    584 585
                         posix/Poll.c
    
    585 586
                         posix/Select.c
    
    587
    +                    posix/SelectBis.c
    
    586 588
                         posix/Signals.c
    
    587 589
                         posix/Timeout.c
    
    588 590
                         posix/TTY.c

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