Duncan Coutts pushed to branch wip/dcoutts/signals-cleanup at Glasgow Haskell Compiler / GHC

Commits:

12 changed files:

Changes:

  • rts/IOManager.c
    ... ... @@ -462,6 +462,20 @@ void startIOManager(void)
    462 462
     
    
    463 463
         switch (iomgr_type) {
    
    464 464
     
    
    465
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    466
    +        case IO_MANAGER_SELECTBIS:
    
    467
    +            for (unsigned int i = 0; i < n_capabilities; i++) {
    
    468
    +                startCapabilityIOManagerSelectBis(getCapability(i)->iomgr);
    
    469
    +            }
    
    470
    +            break;
    
    471
    +#endif
    
    472
    +#if defined(IOMGR_ENABLED_POLL)
    
    473
    +        case IO_MANAGER_POLL:
    
    474
    +            for (unsigned int i = 0; i < n_capabilities; i++) {
    
    475
    +                startCapabilityIOManagerPoll(getCapability(i)->iomgr);
    
    476
    +            }
    
    477
    +            break;
    
    478
    +#endif
    
    465 479
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    466 480
             case IO_MANAGER_MIO_POSIX:
    
    467 481
                 /* Posix implementation in posix/Signals.c
    
    ... ... @@ -514,6 +528,18 @@ restartIOManager(CapIOManager *iomgr, Capability **pcap)
    514 528
     {
    
    515 529
     
    
    516 530
         switch (iomgr_type) {
    
    531
    +#if defined(IOMGR_ENABLED_SELECTBIS)
    
    532
    +        case IO_MANAGER_SELECTBIS:
    
    533
    +            startCapabilityIOManagerSelectBis(iomgr);
    
    534
    +            break;
    
    535
    +#endif
    
    536
    +
    
    537
    +#if defined(IOMGR_ENABLED_POLL)
    
    538
    +        case IO_MANAGER_POLL:
    
    539
    +            startCapabilityIOManagerPoll(iomgr);
    
    540
    +            break;
    
    541
    +#endif
    
    542
    +
    
    517 543
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    518 544
             case IO_MANAGER_MIO_POSIX:
    
    519 545
                 /* Posix implementation in posix/Signals.c
    
    ... ... @@ -530,8 +556,6 @@ restartIOManager(CapIOManager *iomgr, Capability **pcap)
    530 556
                 break;
    
    531 557
     #endif
    
    532 558
             /* The IO_MANAGER_SELECT needs no initialisation */
    
    533
    -        /* The IO_MANAGER_SELECTBIS needs no initialisation */
    
    534
    -        /* The IO_MANAGER_POLL needs no initialisation */
    
    535 559
     
    
    536 560
             /* No impl for any of the Windows I/O managers, since no forking. */
    
    537 561
             default:
    

  • rts/IOManagerInternals.h
    ... ... @@ -76,6 +76,9 @@ struct _CapIOManager {
    76 76
         /* AIOP and timeout collections shared by several I/O manager impls */
    
    77 77
         ClosureTable     aiop_table;
    
    78 78
         StgTimeoutQueue *timeout_queue;
    
    79
    +
    
    80
    +    /* The number of special fds at the start of the aiop table. */
    
    81
    +    int n_special_fds;
    
    79 82
     #endif
    
    80 83
     
    
    81 84
     #if defined(IOMGR_ENABLED_SELECTBIS)
    
    ... ... @@ -83,11 +86,8 @@ struct _CapIOManager {
    83 86
     #endif
    
    84 87
     
    
    85 88
     #if defined(IOMGR_ENABLED_POLL)
    
    86
    -    /* Auxiliary table with size and indexes matching the aiop_table. This is
    
    87
    -     * aliased to the tail of the full poll table, which has a head entry for
    
    88
    -     * the wakeup_fd_r above, so we can also poll that fd.
    
    89
    -     */
    
    90
    -    struct pollfd *aiop_poll_table, *full_poll_table;
    
    89
    +    /* Auxiliary table with size and indexes matching the aiop_table. */
    
    90
    +    struct pollfd *aiop_poll_table;
    
    91 91
     #endif
    
    92 92
     
    
    93 93
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -113,6 +113,17 @@ struct _CapIOManager {
    113 113
      */
    
    114 114
     struct _CapIOManagerShared {
    
    115 115
     
    
    116
    +#if defined(IOMGR_ENABLED_POLL) \
    
    117
    + || defined(IOMGR_ENABLED_SELECTBIS)
    
    118
    +#if defined(RTS_USER_SIGNALS)
    
    119
    +    /* Pipe FDs for delivering posix signals to the I/O manager.
    
    120
    +     * The signal number and siginfo_t are written to the write end by the
    
    121
    +     * low-level signal handler, and read by the I/O manager on capability 0.
    
    122
    +     */
    
    123
    +    int signal_fd_r, signal_fd_w;
    
    124
    +#endif
    
    125
    +#endif
    
    126
    +
    
    116 127
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    117 128
         /* The timer manager control pipe (write end). This is the pipe into which
    
    118 129
          * we will send our signals.
    

  • rts/RtsSymbols.c
    ... ... @@ -1233,6 +1233,9 @@ RTS_SYMBOLS_PRIM
    1233 1233
           (void*)(&(xxx)), strength, ty },
    
    1234 1234
     
    
    1235 1235
     
    
    1236
    +#pragma GCC diagnostic push
    
    1237
    +/* Don't warn about exporting deprecated RTS functions. */
    
    1238
    +#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    
    1236 1239
     
    
    1237 1240
     /* Initialize (if not already initialized) and return an array of symbols with stuff from the RTS. */
    
    1238 1241
     void initLinkerRtsSyms (StrHashTable *symhash) {
    
    ... ... @@ -1270,6 +1273,7 @@ void initLinkerRtsSyms (StrHashTable *symhash) {
    1270 1273
         IF_DEBUG(linker, debugBelch("done with built-in RTS symbols\n"));
    
    1271 1274
     }
    
    1272 1275
     
    
    1276
    +#pragma GCC diagnostic pop
    
    1273 1277
     
    
    1274 1278
     
    
    1275 1279
     // Note [Extra RTS symbols]
    

  • rts/include/rts/BlockSignals.h
    ... ... @@ -33,13 +33,17 @@
    33 33
      * This is exported from the RTS but should no longer be used. It is likely to
    
    34 34
      * be removed in future.
    
    35 35
      */
    
    36
    -void blockUserSignals(void);
    
    36
    +void blockUserSignals(void)
    
    37
    +       __attribute__((deprecated("blockUserSignals should no longer be used, "
    
    38
    +                                 "use pthread_sigmask or similar.")));
    
    37 39
     
    
    38 40
     /*
    
    39 41
      * Function: unblockUserSignals()
    
    40 42
      *
    
    41 43
      * The inverse of blockUserSignals(); re-enable the deliver of console events.
    
    42 44
      */
    
    43
    -void unblockUserSignals(void);
    
    45
    +void unblockUserSignals(void)
    
    46
    +       __attribute__((deprecated("unblockUserSignals should no longer be "
    
    47
    +                                 "used, use pthread_sigmask or similar.")));
    
    44 48
     
    
    45 49
     #endif

  • rts/include/rts/storage/Closures.h
    ... ... @@ -722,14 +722,16 @@ typedef struct {
    722 722
        ------------------------------------------------------------------------- */
    
    723 723
     
    
    724 724
     union NotifyCompletion {
    
    725
    -    StgTSO    *tso;
    
    726
    -    StgMVar   *mvar;
    
    727
    -    StgTVar   *tvar;
    
    725
    +    StgTSO     *tso;
    
    726
    +    StgMVar    *mvar;
    
    727
    +    StgTVar    *tvar;
    
    728
    +    StgClosure *unused;
    
    728 729
     };
    
    729 730
     enum NotifyCompletionType {
    
    730
    -    NotifyTSO  = 0, // thread-synchronous I/O
    
    731
    -    NotifyMVar = 1, // async I/O with MVar notification
    
    732
    -    NotifyTVar = 2  // async I/O with TVar notification
    
    731
    +    NotifyTSO      = 0, // thread-synchronous I/O
    
    732
    +    NotifyMVar     = 1, // async I/O with MVar notification
    
    733
    +    NotifyTVar     = 2, // async I/O with TVar notification
    
    734
    +    NotifyInternal = 3  // used for special I/O manager-internal notification
    
    733 735
     };
    
    734 736
     
    
    735 737
     /* A node in the leftist heap. */
    
    ... ... @@ -873,6 +875,10 @@ typedef struct {
    873 875
     
    
    874 876
               // For failed outcomes, this is the error code.
    
    875 877
             uint32_t error;
    
    878
    +
    
    879
    +          // For notify_type NotifyInternal, this is available for internal
    
    880
    +	  // use by individual I/O managers.
    
    881
    +        uint32_t internal;
    
    876 882
         };
    
    877 883
     
    
    878 884
           // Note that because we use fixed size Ctypes here then the size in words
    

  • rts/posix/IOManSignals.c
    ... ... @@ -92,76 +92,6 @@ more_handlers(int sig)
    92 92
         nHandlers = sig + 1;
    
    93 93
     }
    
    94 94
     
    
    95
    -#if !defined(THREADED_RTS)
    
    96
    -
    
    97
    -#define N_PENDING_HANDLERS 16
    
    98
    -
    
    99
    -siginfo_t pending_handler_buf[N_PENDING_HANDLERS];
    
    100
    -siginfo_t *next_pending_handler = pending_handler_buf;
    
    101
    -
    
    102
    -static inline bool signals_pending(void) {
    
    103
    -    return (next_pending_handler != pending_handler_buf);
    
    104
    -}
    
    105
    -
    
    106
    -#endif /* THREADED_RTS */
    
    107
    -
    
    108
    -/* -----------------------------------------------------------------------------
    
    109
    - * Low-level signal handler
    
    110
    - *
    
    111
    - * Places the requested handler on a stack of pending handlers to be
    
    112
    - * started up at the next context switch.
    
    113
    - * -------------------------------------------------------------------------- */
    
    114
    -
    
    115
    -#if !defined(THREADED_RTS)
    
    116
    -void
    
    117
    -generic_handler(int sig USED_IF_THREADS,
    
    118
    -                siginfo_t *info,
    
    119
    -                void *p STG_UNUSED)
    
    120
    -{
    
    121
    -
    
    122
    -    /* Can't call allocate from here.  Probably can't call malloc
    
    123
    -       either.  However, we have to schedule a new thread somehow.
    
    124
    -
    
    125
    -       It's probably ok to request a context switch and allow the
    
    126
    -       scheduler to  start the handler thread, but how do we
    
    127
    -       communicate this to the scheduler?
    
    128
    -
    
    129
    -       We need some kind of locking, but with low overhead (i.e. no
    
    130
    -       blocking signals every time around the scheduler).
    
    131
    -
    
    132
    -       Signal Handlers are atomic (i.e. they can't be interrupted), and
    
    133
    -       we can make use of this.  We just need to make sure the
    
    134
    -       critical section of the scheduler can't be interrupted - the
    
    135
    -       only way to do this is to block signals.  However, we can lower
    
    136
    -       the overhead by only blocking signals when there are any
    
    137
    -       handlers to run, i.e. the set of pending handlers is
    
    138
    -       non-empty.
    
    139
    -    */
    
    140
    -
    
    141
    -    /* We use a stack to store the pending signals.  We can't
    
    142
    -       dynamically grow this since we can't allocate any memory from
    
    143
    -       within a signal handler.
    
    144
    -
    
    145
    -       Hence unfortunately we have to bomb out if the buffer
    
    146
    -       overflows.  It might be acceptable to carry on in certain
    
    147
    -       circumstances, depending on the signal.
    
    148
    -    */
    
    149
    -
    
    150
    -    memcpy(next_pending_handler, info, sizeof(siginfo_t));
    
    151
    -
    
    152
    -    next_pending_handler++;
    
    153
    -
    
    154
    -    // stack full?
    
    155
    -    if (next_pending_handler == &pending_handler_buf[N_PENDING_HANDLERS]) {
    
    156
    -        errorBelch("too many pending signals");
    
    157
    -        stg_exit(EXIT_FAILURE);
    
    158
    -    }
    
    159
    -
    
    160
    -    interruptCapability(&MainCapability);
    
    161
    -
    
    162
    -}
    
    163
    -#endif /* THREADED_RTS */
    
    164
    -
    
    165 95
     /* -----------------------------------------------------------------------------
    
    166 96
      * Blocking/Unblocking of the user signals
    
    167 97
      * -------------------------------------------------------------------------- */
    
    ... ... @@ -267,34 +197,6 @@ installSignalHandler(int sig, int spi, sigset_t *mask, bool nocldstop,
    267 197
      * Creating new threads for signal handlers.
    
    268 198
      * -------------------------------------------------------------------------- */
    
    269 199
     
    
    270
    -#if !defined(THREADED_RTS)
    
    271
    -void
    
    272
    -startPendingSignalHandlers(Capability *cap)
    
    273
    -{
    
    274
    -  siginfo_t *info;
    
    275
    -  int sig;
    
    276
    -
    
    277
    -  if (!signals_pending()) return;
    
    278
    -
    
    279
    -  blockUserSignals();
    
    280
    -
    
    281
    -  while (next_pending_handler != pending_handler_buf) {
    
    282
    -
    
    283
    -    next_pending_handler--;
    
    284
    -
    
    285
    -    sig = next_pending_handler->si_signo;
    
    286
    -    if (signal_handlers[sig] == STG_SIG_DFL) {
    
    287
    -        continue; // handler has been changed.
    
    288
    -    }
    
    289
    -
    
    290
    -    info = next_pending_handler;
    
    291
    -    startSignalHandler(cap, sig, info);
    
    292
    -  }
    
    293
    -
    
    294
    -  unblockUserSignals();
    
    295
    -}
    
    296
    -#endif
    
    297
    -
    
    298 200
     void startSignalHandler(Capability *cap, int sig, siginfo_t *info)
    
    299 201
     {
    
    300 202
         /* Allocate a copy. This is freed by runHandler. */
    
    ... ... @@ -321,6 +223,43 @@ void startSignalHandler(Capability *cap, int sig, siginfo_t *info)
    321 223
         setThreadLabel(cap, t, "signal handler thread");
    
    322 224
     }
    
    323 225
     
    
    226
    +/* Note [Non-racy signal handling using the self-pipe trick]
    
    227
    + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    228
    + *
    
    229
    + * Reliably picking up unix/posix signals promptly is surprisingly tricky!
    
    230
    + * The standard problem is that there is a race between signal delivery and
    
    231
    + * waiting/blocking in something like select/poll/epoll_wait. The problem is
    
    232
    + * that if one checks for a signal having been delivered (e.g. by looking at a
    
    233
    + * shared variable set by the signal handler) just before calling select/poll
    
    234
    + * then there's a time window between that point and calling select/poll when
    
    235
    + * a signal could be delivered and then not noticed until select/poll returns.
    
    236
    + * Once we've called select/poll then it can return EINTR upon signal arrival,
    
    237
    + * but the window before remains a problem.
    
    238
    + *
    
    239
    + * One solution is to use pselect/ppoll: mask signals, then do a final check
    
    240
    + * for signals having been delivered, then call pselect/ppoll to unmask
    
    241
    + * signals (atomically) for the duration of the wait. This is fine in a single
    
    242
    + * threaded process. In a multi-threaded process, masking signals doesn't work.
    
    243
    + * See Note [pthread_sigmask and the ineffectiveness of masking signals].
    
    244
    + *
    
    245
    + * The standard solution is the "self-pipe trick" where the low level signal
    
    246
    + * handler writes the signal info into a pipe, and poll/select monitors that
    
    247
    + * pipe. This neatly eliminates the window where signals could be missed. If
    
    248
    + * the signal arrives before calling poll/select then the pipe is readable and
    
    249
    + * poll/select will return. If the signal arrives during the poll/select call
    
    250
    + * then they will return with EINTR.
    
    251
    + *
    
    252
    + * In the RTS context, this is done by the I/O managers: the in-RTS ones use
    
    253
    + * newNonBlockingPipe on startup, have their low level signal handler use
    
    254
    + * writeSignalInfoToPipe, and when the I/O manager finds the other end of the
    
    255
    + * pipe is readable then it calls readSignalInfoFromPipe, and can then use
    
    256
    + * startSignalHandler.
    
    257
    + *
    
    258
    + * The in-Haskell MIO I/O manager does essentially the same.
    
    259
    + *
    
    260
    + * See issue #27540.
    
    261
    + */
    
    262
    +
    
    324 263
     void newNonBlockingPipe(int *pipe_fd_r, int *pipe_fd_w)
    
    325 264
     {
    
    326 265
         int pipefd[2];
    

  • rts/posix/IOManSignals.h
    ... ... @@ -25,16 +25,6 @@ void initUserSignals(void);
    25 25
      */
    
    26 26
     void finiUserSignals(void);
    
    27 27
     
    
    28
    -#if !defined(THREADED_RTS)
    
    29
    -/*
    
    30
    - * Function: startPendingSignalHandlers()
    
    31
    - *
    
    32
    - * If there are any queued up posix signals, run the handlers
    
    33
    - * associated with them.
    
    34
    - */
    
    35
    -void startPendingSignalHandlers(Capability *cap);
    
    36
    -#endif
    
    37
    -
    
    38 28
     void startSignalHandler(Capability *cap, int sig, siginfo_t *info);
    
    39 29
     
    
    40 30
     typedef void (*SignalHandler)(int sig, siginfo_t *info, void *p);
    
    ... ... @@ -47,11 +37,6 @@ void newNonBlockingPipe(int *pipe_fd_r, int *pipe_fd_w);
    47 37
     void writeSignalInfoToPipe (int  sig, siginfo_t *info, int fd);
    
    48 38
     bool readSignalInfoFromPipe(int *sig, siginfo_t *info, int fd);
    
    49 39
     
    
    50
    -/* Temporary */
    
    51
    -void generic_handler(int sig USED_IF_THREADS,
    
    52
    -                     siginfo_t *info,
    
    53
    -                     void *p STG_UNUSED);
    
    54
    -
    
    55 40
     #endif
    
    56 41
     
    
    57 42
     #include "EndPrivate.h"
    

  • rts/posix/Poll.c
    1 1
     /* -----------------------------------------------------------------------------
    
    2 2
      *
    
    3
    - * (c) The GHC Team 2020-2023
    
    3
    + * (c) The GHC Team 2020-2026
    
    4 4
      *
    
    5 5
      * An I/O manager based on the classic Unix poll() system call.
    
    6 6
      *
    
    ... ... @@ -107,9 +107,10 @@ timeout (if any) as the poll() timeout parameter.
    107 107
     The CapIOManager structure for this I/O manager contains:
    
    108 108
     
    
    109 109
         ClosureTable     aiop_table;
    
    110
    -    struct pollfd   *aiop_poll_table, *full_poll_table;
    
    110
    +    struct pollfd   *aiop_poll_table;
    
    111 111
         StgTimeoutQueue *timeout_queue;
    
    112 112
         int interrupt_fd_r, interrupt_fd_w;
    
    113
    +    int n_special_fds;
    
    113 114
     
    
    114 115
     We also support the Linux-specific ppoll API which supports higher resolution
    
    115 116
     time delays -- nanoseconds rather than milliseconds as in classic poll(). It
    
    ... ... @@ -118,14 +119,15 @@ also allows the signal mask to be adjusted, but we do not make use of this.
    118 119
        int ppoll(struct pollfd *fds, nfds_t nfds,
    
    119 120
                const struct timespec *tmo_p, const sigset_t *sigmask);
    
    120 121
     
    
    121
    -We have both aiop_poll_table and full_poll_table. This is to cope with needing
    
    122
    -to wait on the special extra file descriptor interrupt_fd_r. This fd is used to
    
    123
    -support waking the I/O manager when we are blocked in a poll call. This
    
    124
    -requires waiting on an extra fd that has no corresponding entry in the
    
    125
    -aiop_table. To manage this quirk, we alias the aiop_poll_table to be the tail
    
    126
    -of the full_poll_table and have the first entry of the full_poll_table be the
    
    127
    -interrupt_fd_r. This means the aiop_poll_table indicies match up exactly with
    
    128
    -the aiop_table, but still allows the full_poll_table to have an extra entry.
    
    122
    +In addition to aiops, there are some special internal fds we have to monitor
    
    123
    +and respond to. In particular there is the interrupt_fd_r used to implement
    
    124
    +interruptIOManager. Whether this fd is used or not depends on configuration
    
    125
    +(use of interrupt_fd_r depends on HAVE_PREEMPTION). To enable multiple such
    
    126
    +fds and allow them to be optional we use the following scheme: we create
    
    127
    +special internal AsyncIOOps and put them into the aiop_table. These are
    
    128
    +treated much like other IOOpCodeWaitRead operations but with special handling
    
    129
    +for notification. The variable n_special_fds tracks how many such entries we
    
    130
    +have so that we can handle them specially but in a reasonably uniform manner.
    
    129 131
     
    
    130 132
     ******************************************************************************/
    
    131 133
     
    
    ... ... @@ -135,7 +137,20 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    135 137
     static void removeFromTables(CapIOManager *iomgr, int i);
    
    136 138
     static void reportPollError(int res, nfds_t nfds) STG_NORETURN;
    
    137 139
     
    
    140
    +enum SpecialFd {
    
    141
    +    InterruptFd,  /* iomgr->interrupt_fd_r for interruptIOManager */
    
    142
    +    SignalFd      /* iomgr->shared->signal_fd_r */
    
    143
    +};
    
    144
    +static void insertSpecialFd(CapIOManager  *iomgr,
    
    145
    +                            enum SpecialFd special_fd_kind,
    
    146
    +                            int            special_fd);
    
    147
    +static void processSpecialFd(CapIOManager *iomgr,
    
    148
    +                             StgAsyncIOOp *aiop, bool *interrupt);
    
    138 149
     
    
    150
    +
    
    151
    +/* Called early in RTS startup _before_ the storage manager is initialised,
    
    152
    + * so this is not allowed to allocate on the GC heap.
    
    153
    + */
    
    139 154
     void initCapabilityIOManagerPoll(CapIOManager *iomgr)
    
    140 155
     {
    
    141 156
         initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
    
    ... ... @@ -145,26 +160,80 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr)
    145 160
         newFdWakeup(&iomgr->interrupt_fd_r, &iomgr->interrupt_fd_w);
    
    146 161
     #endif
    
    147 162
     
    
    148
    -    iomgr->full_poll_table = stgMallocBytes(sizeof(struct pollfd) /* size 1 */,
    
    149
    -                                            "initCapabilityIOManagerPoll");
    
    150
    -    iomgr->full_poll_table[0] = (struct pollfd) {
    
    163
    +    iomgr->n_special_fds = 0
    
    151 164
     #if defined(HAVE_PREEMPTION)
    
    152
    -                                  .fd      = iomgr->interrupt_fd_r,
    
    153
    -                                  .events  = POLLIN,
    
    154
    -#else
    
    155
    -                                  .fd      = -1, // unused
    
    156
    -                                  .events  = 0,  // unused
    
    165
    +        + 1
    
    166
    +#endif
    
    167
    +#if defined(RTS_USER_SIGNALS)
    
    168
    +        + (iomgr->cap->no == 0 ? 1 : 0)
    
    157 169
     #endif
    
    158
    -                                  .revents = 0
    
    159
    -                                };
    
    160
    -    iomgr->aiop_poll_table = iomgr->full_poll_table+1; /* hence empty */
    
    170
    +        ;
    
    161 171
     
    
    162 172
     #if defined(RTS_USER_SIGNALS)
    
    163 173
         /* Signal handlers are shared across all caps. Only init once. */
    
    164 174
         if (iomgr->cap->no == 0) {
    
    165 175
             initUserSignals();
    
    176
    +        /* See Note [Non-racy signal handling using the self-pipe trick] */
    
    177
    +        newNonBlockingPipe(&iomgr->shared->signal_fd_r,
    
    178
    +                           &iomgr->shared->signal_fd_w);
    
    179
    +    }
    
    180
    +#endif
    
    181
    +}
    
    182
    +
    
    183
    +
    
    184
    +/* Called late in RtsStartup, once heap allocation is ready. */
    
    185
    +void startCapabilityIOManagerPoll(CapIOManager *iomgr)
    
    186
    +{
    
    187
    +    /* ClosureTable allocates on the heap, so we can do it here not in init */
    
    188
    +    if (iomgr->n_special_fds > 0) {
    
    189
    +        bool ok = enlargeClosureTable(iomgr->cap, &iomgr->aiop_table,
    
    190
    +                                      iomgr->n_special_fds);
    
    191
    +        CHECK(ok);
    
    166 192
         }
    
    193
    +    iomgr->aiop_poll_table =
    
    194
    +        stgMallocBytes(sizeof(struct pollfd) * iomgr->n_special_fds,
    
    195
    +                       "initCapabilityIOManagerPoll");
    
    196
    +
    
    197
    +#if defined(HAVE_PREEMPTION)
    
    198
    +    insertSpecialFd(iomgr, InterruptFd, iomgr->interrupt_fd_r);
    
    167 199
     #endif
    
    200
    +#if defined(RTS_USER_SIGNALS)
    
    201
    +    if (iomgr->cap->no == 0) {
    
    202
    +        /* See Note [Non-racy signal handling using the self-pipe trick] */
    
    203
    +        insertSpecialFd(iomgr, SignalFd, iomgr->shared->signal_fd_r);
    
    204
    +    }
    
    205
    +#endif
    
    206
    +    ASSERT(iomgr->n_special_fds == sizeClosureTable(&iomgr->aiop_table));
    
    207
    +}
    
    208
    +
    
    209
    +
    
    210
    +/* For special fds, we add a dummy entry to the aiop_table and a
    
    211
    + * corresponding real entry to the aiop_poll_table.
    
    212
    + */
    
    213
    +static void insertSpecialFd(CapIOManager  *iomgr,
    
    214
    +                            enum SpecialFd special_fd_kind,
    
    215
    +                            int            special_fd)
    
    216
    +{
    
    217
    +    StgAsyncIOOp *aiop  = (StgAsyncIOOp *)allocate(iomgr->cap,
    
    218
    +                                                   sizeofW(StgAsyncIOOp));
    
    219
    +    SET_HDR(aiop, &stg_ASYNCIOOP_info, CCS_SYSTEM);
    
    220
    +    aiop->notify.unused = &stg_ASYNCIO_LIVE0_closure;
    
    221
    +    aiop->live          = &stg_ASYNCIO_LIVE0_closure;
    
    222
    +    aiop->capno         = iomgr->cap->no;
    
    223
    +    aiop->notify_type   = NotifyInternal;
    
    224
    +    aiop->outcome       = IOOpOutcomeInFlight;
    
    225
    +    aiop->operation     = IOOpCodeWaitRead;
    
    226
    +    aiop->fd            = special_fd;
    
    227
    +    aiop->internal      = special_fd_kind;
    
    228
    +
    
    229
    +    int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop);
    
    230
    +    aiop->index = ix;
    
    231
    +
    
    232
    +    iomgr->aiop_poll_table[ix] = (struct pollfd) {
    
    233
    +                                     .fd      = special_fd,
    
    234
    +                                     .events  = POLLIN,
    
    235
    +                                     .revents = 0
    
    236
    +                                 };
    
    168 237
     }
    
    169 238
     
    
    170 239
     
    
    ... ... @@ -173,9 +242,11 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr)
    173 242
     #if defined(RTS_USER_SIGNALS)
    
    174 243
         if (iomgr->cap->no == 0) {
    
    175 244
             finiUserSignals();
    
    245
    +        close(iomgr->shared->signal_fd_r);
    
    246
    +        close(iomgr->shared->signal_fd_w);
    
    176 247
         }
    
    177 248
     #endif
    
    178
    -    stgFree(iomgr->full_poll_table);
    
    249
    +    stgFree(iomgr->aiop_poll_table);
    
    179 250
     #if defined(HAVE_PREEMPTION)
    
    180 251
         closeFdWakeup(iomgr->interrupt_fd_r, iomgr->interrupt_fd_w);
    
    181 252
     #endif
    
    ... ... @@ -314,22 +385,10 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    314 385
         ASSERT(ncompletions > 0);
    
    315 386
     
    
    316 387
         bool interrupt = false;
    
    317
    -#if defined(HAVE_PREEMPTION)
    
    318
    -    /* If the interrupt_fd_r is ready, collect it */
    
    319
    -    if (iomgr->full_poll_table[0].revents) {
    
    320
    -        ASSERT(iomgr->full_poll_table[0].fd == iomgr->interrupt_fd_r);
    
    321
    -        collectFdWakeup(iomgr->interrupt_fd_r);
    
    322
    -        ncompletions--;
    
    323
    -        interrupt = true;
    
    324
    -        debugTrace(DEBUG_iomanager, "Received interrupt in poll I/O manager");
    
    325
    -    }
    
    326
    -#endif
    
    327 388
     
    
    328 389
         struct pollfd *aiop_poll_table = iomgr->aiop_poll_table;
    
    329 390
         int i = 0;
    
    330
    -    while (ncompletions > 0) {
    
    331
    -        ASSERT(i < sizeClosureTable(&iomgr->aiop_table));
    
    332
    -
    
    391
    +    while (ncompletions > 0 && i < sizeClosureTable(&iomgr->aiop_table)) {
    
    333 392
             /* Since each aiop_table entry is for a single (fd, rw) pair, we
    
    334 393
              * just need to check if the aiop_poll_table[n].revents contains
    
    335 394
              * anything. We don't need to distinguish read vs write.
    
    ... ... @@ -337,26 +396,34 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    337 396
             if (aiop_poll_table[i].revents) {
    
    338 397
                 StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, i);
    
    339 398
     
    
    399
    +            ASSERT(aiop->operation == IOOpCodeWaitRead ||
    
    400
    +                   aiop->operation == IOOpCodeWaitWrite);
    
    340 401
                 ASSERT(aiop->outcome == IOOpOutcomeInFlight);
    
    341 402
     
    
    342
    -            /* We do need to handle POLLNVAL, but we do not need to do anything
    
    343
    -             * special for POLLERR or POLLHUP. (See man poll for details).
    
    344
    -             * The calling thread will typically try to do I/O after waiting
    
    345
    -             * for I/O readiness. Thus for POLLERR they'll discover the error
    
    346
    -             * when doing the I/O. And for POLLHUP, there is typically no
    
    347
    -             * action required anyway: it's a notification and not an error.
    
    348
    -             */
    
    349
    -            if (aiop_poll_table[i].revents & POLLNVAL) {
    
    350
    -                aiop->outcome = IOOpOutcomeFailed;
    
    351
    -                aiop->error   = EBADF;
    
    403
    +            if (aiop->notify_type == NotifyInternal) {
    
    404
    +                processSpecialFd(iomgr, aiop, &interrupt);
    
    405
    +                ncompletions--;
    
    406
    +                i++;
    
    352 407
                 } else {
    
    353
    -                aiop->outcome = IOOpOutcomeSuccess;
    
    354
    -                aiop->result  = 0;
    
    408
    +                /* We do need to handle POLLNVAL, but we do not need to do anything
    
    409
    +                 * special for POLLERR or POLLHUP. (See man poll for details).
    
    410
    +                 * The calling thread will typically try to do I/O after waiting
    
    411
    +                 * for I/O readiness. Thus for POLLERR they'll discover the error
    
    412
    +                 * when doing the I/O. And for POLLHUP, there is typically no
    
    413
    +                 * action required anyway: it's a notification and not an error.
    
    414
    +                 */
    
    415
    +                if (aiop_poll_table[i].revents & POLLNVAL) {
    
    416
    +                    aiop->outcome = IOOpOutcomeFailed;
    
    417
    +                    aiop->error   = EBADF;
    
    418
    +                } else {
    
    419
    +                    aiop->outcome = IOOpOutcomeSuccess;
    
    420
    +                    aiop->result  = 0;
    
    421
    +                }
    
    422
    +
    
    423
    +                removeFromTables(iomgr, i);
    
    424
    +                notifyIOCompletion(iomgr, aiop);
    
    425
    +                ncompletions--;
    
    355 426
                 }
    
    356
    -
    
    357
    -            removeFromTables(iomgr, i);
    
    358
    -            notifyIOCompletion(iomgr, aiop);
    
    359
    -            ncompletions--;
    
    360 427
             } else {
    
    361 428
                 /* You'd expect incrementing the table index to be unconditional,
    
    362 429
                  * but we don't increment the index if we did process the entry,
    
    ... ... @@ -366,14 +433,44 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    366 433
                 i++;
    
    367 434
             }
    
    368 435
         }
    
    436
    +    ASSERT(ncompletions == 0);
    
    369 437
         return interrupt;
    
    370 438
     }
    
    371 439
     
    
    372 440
     
    
    373
    -void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    
    441
    +static void processSpecialFd(CapIOManager *iomgr,
    
    442
    +                             StgAsyncIOOp *aiop, bool *interrupt)
    
    374 443
     {
    
    375
    -    ASSERT(iomgr->aiop_poll_table == iomgr->full_poll_table+1);
    
    444
    +    ASSERT(aiop->notify_type == NotifyInternal);
    
    445
    +    enum SpecialFd special_fd_kind = aiop->internal;
    
    446
    +    int fd = aiop->fd;
    
    447
    +
    
    448
    +    switch (special_fd_kind) {
    
    449
    +#if defined(HAVE_PREEMPTION)
    
    450
    +        case InterruptFd:
    
    451
    +            ASSERT(fd == iomgr->interrupt_fd_r);
    
    452
    +            collectFdWakeup(fd);
    
    453
    +            *interrupt = true;
    
    454
    +            debugTrace(DEBUG_iomanager, "Received interrupt in poll I/O manager");
    
    455
    +            break;
    
    456
    +#endif
    
    457
    +#if defined(RTS_USER_SIGNALS)
    
    458
    +        /* See Note [Non-racy signal handling using the self-pipe trick] */
    
    459
    +        case SignalFd:
    
    460
    +            ASSERT(fd == iomgr->shared->signal_fd_r);
    
    461
    +            int sig; siginfo_t info;
    
    462
    +            readSignalInfoFromPipe(&sig, &info, fd);
    
    463
    +            startSignalHandler(iomgr->cap, sig, &info);
    
    464
    +            debugTrace(DEBUG_iomanager,
    
    465
    +                       "Dispatched signal %d in I/O manager", sig);
    
    466
    +            break;
    
    467
    +#endif
    
    468
    +    }
    
    469
    +}
    
    470
    +
    
    376 471
     
    
    472
    +void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    
    473
    +{
    
    377 474
         if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
    
    378 475
             Time now = getProcessElapsedTime();
    
    379 476
             processTimeoutCompletions(iomgr, now);
    
    ... ... @@ -381,29 +478,20 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    381 478
     
    
    382 479
         if (!isEmptyClosureTable(&iomgr->aiop_table)) {
    
    383 480
     
    
    384
    -        int n_aiops = sizeClosureTable(&iomgr->aiop_table);
    
    385
    -#if defined(HAVE_PREEMPTION)
    
    386
    -        /* the full_poll_table includes interrupt_fd_r */
    
    387
    -        nfds_t nfds = n_aiops + 1;
    
    388
    -        struct pollfd *poll_table = iomgr->full_poll_table;
    
    389
    -#else
    
    390
    -        /* the aiop_poll_table does not include interrupt_fd_r */
    
    391
    -        nfds_t nfds = n_aiops + 0;
    
    392
    -        struct pollfd *poll_table = iomgr->aiop_poll_table;
    
    393
    -#endif
    
    481
    +        nfds_t nfds = sizeClosureTable(&iomgr->aiop_table);
    
    394 482
     
    
    395 483
             /* Poll for I/O readiness, without waiting. */
    
    396 484
     #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    397 485
             /* We could use poll here, since we use no timeout, but for
    
    398 486
                consistency we use the same syscall as at the other call site. */
    
    399 487
             struct timespec tv = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 };
    
    400
    -        int res = ppoll(poll_table, nfds, &tv, NULL);
    
    488
    +        int res = ppoll(iomgr->aiop_poll_table, nfds, &tv, NULL);
    
    401 489
     
    
    402 490
             debugTrace(DEBUG_iomanager,
    
    403 491
                        "ppoll(nfds = %lu, timeout = 0) = %d",
    
    404 492
                        (unsigned long) nfds, res);
    
    405 493
     #else
    
    406
    -        int res = poll(poll_table, nfds, 0);
    
    494
    +        int res = poll(iomgr->aiop_poll_table, nfds, 0);
    
    407 495
     
    
    408 496
             debugTrace(DEBUG_iomanager,
    
    409 497
                        "poll(nfds = %lu, timeout = 0) = %d",
    
    ... ... @@ -426,10 +514,6 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    426 514
                 reportPollError(res, nfds);
    
    427 515
             }
    
    428 516
         }
    
    429
    -
    
    430
    -#if defined(RTS_USER_SIGNALS)
    
    431
    -    startPendingSignalHandlers(iomgr->cap);
    
    432
    -#endif
    
    433 517
     }
    
    434 518
     
    
    435 519
     
    
    ... ... @@ -437,8 +521,6 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    437 521
     {
    
    438 522
         bool interrupt = false; /* got woken up via interruptIOManager */
    
    439 523
     
    
    440
    -    ASSERT(iomgr->aiop_poll_table == iomgr->full_poll_table+1);
    
    441
    -
    
    442 524
         /* Loop until we've woken up some threads. This loop is needed because the
    
    443 525
          * poll() timing isn't accurate, we sometimes sleep for a while but not
    
    444 526
          * long enough to wake up a thread in a threadDelay. Or we may need to
    
    ... ... @@ -461,22 +543,14 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    461 543
              */
    
    462 544
             bool wait = emptyRunQueue(iomgr->cap);
    
    463 545
     
    
    464
    -        int n_aiops = sizeClosureTable(&iomgr->aiop_table);
    
    465
    -#if defined(HAVE_PREEMPTION)
    
    466
    -        /* the full_poll_table includes interrupt_fd_r */
    
    467
    -        nfds_t nfds = n_aiops + 1;
    
    468
    -        struct pollfd *poll_table = iomgr->full_poll_table;
    
    469
    -#else
    
    470
    -        /* the aiop_poll_table does not include interrupt_fd_r */
    
    471
    -        nfds_t nfds = n_aiops + 0;
    
    472
    -        struct pollfd *poll_table = iomgr->aiop_poll_table;
    
    473
    -#endif
    
    474
    -
    
    475 546
             /* Decide if we are going to wait if no I/O is ready, either:
    
    476 547
              * poll only, wait indefinitely, or wait until a timeout.
    
    477 548
              */
    
    478 549
             Time timeout = timeoutWaitTime(iomgr, wait, now);
    
    479 550
     
    
    551
    +        nfds_t nfds = sizeClosureTable(&iomgr->aiop_table);
    
    552
    +        int n_aiops = nfds - iomgr->n_special_fds;
    
    553
    +
    
    480 554
             if (RTS_UNLIKELY(timeout == -1 && n_aiops == 0)) {
    
    481 555
                 /* See Note [Deadlock detection without idle GC] */
    
    482 556
                 interrupt = notifyIdleGcDeadlock();
    
    ... ... @@ -489,7 +563,7 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    489 563
              * time, and if that timeout is reached then we schedule an idle GC
    
    490 564
              * (see handleIdleGcTimeout below).
    
    491 565
              */
    
    492
    -        bool any_pending_io = nfds > 0;
    
    566
    +        bool any_pending_io = n_aiops > 0;
    
    493 567
             bool timeout_idlegc_delay;
    
    494 568
             adjustTimeoutForIdleGc(any_pending_io, &timeout, &timeout_idlegc_delay);
    
    495 569
     #endif
    
    ... ... @@ -497,14 +571,14 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    497 571
             /* Check for I/O readiness, possibly waiting. */
    
    498 572
     #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    499 573
             struct timespec ts, *timeout_ns = timeoutAsTimespec(timeout, &ts);
    
    500
    -        int res = ppoll(poll_table, nfds, timeout_ns, NULL);
    
    574
    +        int res = ppoll(iomgr->aiop_poll_table, nfds, timeout_ns, NULL);
    
    501 575
     
    
    502 576
             debugTrace(DEBUG_iomanager,
    
    503 577
                        "ppoll(nfds = %lu, timeout = %" FMT_Int64 " ns) = %d",
    
    504 578
                        (unsigned long) nfds, timeout, res);
    
    505 579
     #else
    
    506 580
             int timeout_ms = timeoutAsPollTimeout(timeout);
    
    507
    -        int res = poll(poll_table, nfds, timeout_ms);
    
    581
    +        int res = poll(iomgr->aiop_poll_table, nfds, timeout_ms);
    
    508 582
     
    
    509 583
             debugTrace(DEBUG_iomanager,
    
    510 584
                        "poll(nfds = %lu, timeout = %d ms) = %d",
    
    ... ... @@ -534,20 +608,23 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    534 608
                  */
    
    535 609
     
    
    536 610
             } else if (errno == EINTR) {
    
    537
    -            /* We got interrupted by a signal. */
    
    538
    -
    
    539
    -#if defined(RTS_USER_SIGNALS)
    
    540
    -            /* Start any corresponding user signal handlers. If any, the run
    
    541
    -             * queue will become non-empty and we will drop out of the loop.
    
    542
    -             */
    
    543
    -            startPendingSignalHandlers(iomgr->cap);
    
    544
    -#endif
    
    545
    -
    
    546
    -            /* We can also be interrupted by the shutdown signal handler, which
    
    547
    -             * will set sched_state and so cause us to drop out of the loop.
    
    611
    +            /* We got interrupted by a signal.
    
    612
    +             *
    
    613
    +             * Signals of note:
    
    614
    +             *  + signals with a user handler: these will be delivered to us
    
    615
    +             *    via the signal_fd_r pipe, which we handle by starting new
    
    616
    +             *    threads, which will terminate the loop (non-empty run queue).
    
    617
    +             *  + The backup ctl-c signal handler sets sched_state, and to avoid
    
    618
    +             *    races, it also calls interruptIOManagerPoll (which causes
    
    619
    +             *    interrupt_fd_r to become ready). We're in the EINTR case here
    
    620
    +             *    so we know the signal arrived while we were waiting, so it is
    
    621
    +             *    the getSchedState condition that will will terminate the loop.
    
    622
    +             *    (If the signal arrives before the call to poll then we will
    
    623
    +             *    notice interrupt_fd_r is ready and set interrupt=true, which
    
    624
    +             *    will terminate the loop).
    
    548 625
                  *
    
    549
    -             * For any other interruption (e.g. timer) we will go round the
    
    550
    -             * do-while loop again.
    
    626
    +             * For any other interruption we will go round the do-while loop
    
    627
    +             * again.
    
    551 628
                  */
    
    552 629
     
    
    553 630
             } else {
    
    ... ... @@ -585,11 +662,20 @@ void interruptIOManagerPoll(CapIOManager *iomgr USED_IF_PREEMPTION)
    585 662
     }
    
    586 663
     
    
    587 664
     
    
    665
    +#if defined(RTS_USER_SIGNALS)
    
    666
    +static void signal_handler(int sig, siginfo_t *info, void *p STG_UNUSED)
    
    667
    +{
    
    668
    +    int signal_fd_w = getCapability(0)->iomgr->shared->signal_fd_w;
    
    669
    +    writeSignalInfoToPipe(sig, info, signal_fd_w);
    
    670
    +}
    
    671
    +
    
    672
    +
    
    588 673
     int installUserSignalHandlerPoll(int sig, int spi, sigset_t *mask,
    
    589 674
                                      bool nocldstop)
    
    590 675
     {
    
    591
    -    return installSignalHandler(sig, spi, mask, nocldstop, generic_handler);
    
    676
    +    return installSignalHandler(sig, spi, mask, nocldstop, signal_handler);
    
    592 677
     }
    
    678
    +#endif
    
    593 679
     
    
    594 680
     
    
    595 681
     /* Helper function to double the size of the aiop_table and aiop_poll_table.
    
    ... ... @@ -602,14 +688,11 @@ static bool enlargeTables(CapIOManager *iomgr)
    602 688
         bool ok = enlargeClosureTable(iomgr->cap, &iomgr->aiop_table, newcapacity);
    
    603 689
         if (RTS_UNLIKELY(!ok)) return false;
    
    604 690
     
    
    605
    -    /* Update the auxiliary aiop_poll_table to match. The full_poll_table is
    
    606
    -     * one bigger than the aiop_poll_table, since it has an extra entry at the
    
    607
    -     * front for interrupt_fd_r, with no corresponding aiop. */
    
    608
    -    iomgr->full_poll_table =
    
    609
    -        stgReallocBytes(iomgr->full_poll_table,
    
    610
    -                        sizeof(struct pollfd) * (newcapacity+1),
    
    691
    +    /* Update the auxiliary aiop_poll_table to match. */
    
    692
    +    iomgr->aiop_poll_table =
    
    693
    +        stgReallocBytes(iomgr->aiop_poll_table,
    
    694
    +                        sizeof(struct pollfd) * newcapacity,
    
    611 695
                             "Poll.c: enlargeTables");
    
    612
    -    iomgr->aiop_poll_table = iomgr->full_poll_table+1;
    
    613 696
     
    
    614 697
         /* Initialise the new part of the aiop_poll_table */
    
    615 698
         struct pollfd *aiop_poll_table = iomgr->aiop_poll_table;
    

  • rts/posix/Poll.h
    ... ... @@ -18,6 +18,7 @@
    18 18
     
    
    19 19
     void initCapabilityIOManagerPoll(CapIOManager *iomgr);
    
    20 20
     void freeCapabilityIOManagerPoll(CapIOManager *iomgr);
    
    21
    +void startCapabilityIOManagerPoll(CapIOManager *iomgr);
    
    21 22
     
    
    22 23
     /* Synchronous I/O and timer operations */
    
    23 24
     IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    

  • rts/posix/Select.c
    ... ... @@ -55,6 +55,8 @@
    55 55
     #define TimeToLowResTimeRoundUp(t)   (t)
    
    56 56
     #endif
    
    57 57
     
    
    58
    +static void startPendingSignalHandlers(Capability *cap);
    
    59
    +
    
    58 60
     void initCapabilityIOManagerSelect(CapIOManager *iomgr)
    
    59 61
     {
    
    60 62
         iomgr->blocked_queue_hd = END_TSO_QUEUE;
    
    ... ... @@ -524,10 +526,97 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait)
    524 526
         return !interrupt;
    
    525 527
     }
    
    526 528
     
    
    529
    +
    
    530
    +/* -----------------------------------------------------------------------------
    
    531
    + * Low-level signal handler
    
    532
    + *
    
    533
    + * Places the requested handler on a stack of pending handlers to be
    
    534
    + * started up at the next context switch.
    
    535
    + * -------------------------------------------------------------------------- */
    
    536
    +
    
    537
    +#define N_PENDING_HANDLERS 16
    
    538
    +
    
    539
    +siginfo_t pending_handler_buf[N_PENDING_HANDLERS];
    
    540
    +siginfo_t *next_pending_handler = pending_handler_buf;
    
    541
    +
    
    542
    +static void
    
    543
    +signal_handler(int sig STG_UNUSED, siginfo_t *info, void *p STG_UNUSED)
    
    544
    +{
    
    545
    +
    
    546
    +    /* Can't call allocate from here.  Probably can't call malloc
    
    547
    +       either.  However, we have to schedule a new thread somehow.
    
    548
    +
    
    549
    +       It's probably ok to request a context switch and allow the
    
    550
    +       scheduler to  start the handler thread, but how do we
    
    551
    +       communicate this to the scheduler?
    
    552
    +
    
    553
    +       We need some kind of locking, but with low overhead (i.e. no
    
    554
    +       blocking signals every time around the scheduler).
    
    555
    +
    
    556
    +       Signal Handlers are atomic (i.e. they can't be interrupted), and
    
    557
    +       we can make use of this.  We just need to make sure the
    
    558
    +       critical section of the scheduler can't be interrupted - the
    
    559
    +       only way to do this is to block signals.  However, we can lower
    
    560
    +       the overhead by only blocking signals when there are any
    
    561
    +       handlers to run, i.e. the set of pending handlers is
    
    562
    +       non-empty.
    
    563
    +    */
    
    564
    +
    
    565
    +    /* We use a stack to store the pending signals.  We can't
    
    566
    +       dynamically grow this since we can't allocate any memory from
    
    567
    +       within a signal handler.
    
    568
    +
    
    569
    +       Hence unfortunately we have to bomb out if the buffer
    
    570
    +       overflows.  It might be acceptable to carry on in certain
    
    571
    +       circumstances, depending on the signal.
    
    572
    +    */
    
    573
    +
    
    574
    +    memcpy(next_pending_handler, info, sizeof(siginfo_t));
    
    575
    +
    
    576
    +    next_pending_handler++;
    
    577
    +
    
    578
    +    // stack full?
    
    579
    +    if (next_pending_handler == &pending_handler_buf[N_PENDING_HANDLERS]) {
    
    580
    +        errorBelch("too many pending signals");
    
    581
    +        stg_exit(EXIT_FAILURE);
    
    582
    +    }
    
    583
    +
    
    584
    +    interruptCapability(&MainCapability);
    
    585
    +
    
    586
    +}
    
    587
    +
    
    527 588
     int installUserSignalHandlerSelect(int sig, int spi, sigset_t *mask,
    
    528 589
                                        bool nocldstop)
    
    529 590
     {
    
    530
    -    return installSignalHandler(sig, spi, mask, nocldstop, generic_handler);
    
    591
    +    return installSignalHandler(sig, spi, mask, nocldstop, signal_handler);
    
    592
    +}
    
    593
    +
    
    594
    +static void startPendingSignalHandlers(Capability *cap)
    
    595
    +{
    
    596
    +    if (next_pending_handler == pending_handler_buf) return;
    
    597
    +
    
    598
    +    // Temporarily mask all signals to stop the signal_handler from mutating
    
    599
    +    // next_pending_handler and pending_handler_buf while we are looking at
    
    600
    +    // and modifying them.
    
    601
    +    static sigset_t maskSignals, savedSignals;
    
    602
    +    sigfillset(&maskSignals);
    
    603
    +    pthread_sigmask(SIG_SETMASK, &maskSignals, &savedSignals);
    
    604
    +
    
    605
    +    while (next_pending_handler != pending_handler_buf) {
    
    606
    +
    
    607
    +        next_pending_handler--;
    
    608
    +
    
    609
    +        siginfo_t *info = next_pending_handler;
    
    610
    +        int        sig  = info->si_signo;
    
    611
    +        startSignalHandler(cap, sig, info);
    
    612
    +
    
    613
    +        // It's possible by the time the signal handler runs that the handler
    
    614
    +        // has changed: runHandlers will run whatever handler is set at the
    
    615
    +        // time (or none).
    
    616
    +    }
    
    617
    +
    
    618
    +    // Restore the signal mask.
    
    619
    +    pthread_sigmask(SIG_SETMASK, &savedSignals, NULL);
    
    531 620
     }
    
    532 621
     
    
    533 622
     #endif /* IOMGR_ENABLED_SELECT */

  • rts/posix/SelectBis.c
    ... ... @@ -121,6 +121,17 @@ The CapIOManager structure for this I/O manager contains:
    121 121
         ClosureTable     aiop_table;
    
    122 122
         StgTimeoutQueue *timeout_queue;
    
    123 123
         int interrupt_fd_r, interrupt_fd_w;
    
    124
    +    int n_special_fds;
    
    125
    +
    
    126
    +In addition to aiops, there are some special internal fds we have to monitor
    
    127
    +and respond to. In particular there is the interrupt_fd_r used to implement
    
    128
    +interruptIOManager. Whether this fd is used or not depends on configuration
    
    129
    +(use of interrupt_fd_r depends on HAVE_PREEMPTION). To enable multiple such
    
    130
    +fds and allow them to be optional we use the following scheme: we create
    
    131
    +special internal AsyncIOOps and put them into the aiop_table. These are
    
    132
    +treated much like other IOOpCodeWaitRead operations but with special handling
    
    133
    +for notification. The variable n_special_fds tracks how many such entries we
    
    134
    +have so that we can handle them specially but in a reasonably uniform manner.
    
    124 135
     
    
    125 136
     ******************************************************************************/
    
    126 137
     
    
    ... ... @@ -133,7 +144,20 @@ static int collectFdSets(CapIOManager *iomgr);
    133 144
     static void processBadFds(CapIOManager *iomgr);
    
    134 145
     static void reportSelectError(void) STG_NORETURN;
    
    135 146
     
    
    147
    +enum SpecialFd {
    
    148
    +    InterruptFd,  /* iomgr->interrupt_fd_r for interruptIOManager */
    
    149
    +    SignalFd      /* iomgr->shared->signal_fd_r */
    
    150
    +};
    
    151
    +static void insertSpecialFd(CapIOManager  *iomgr,
    
    152
    +                            enum SpecialFd special_fd_kind,
    
    153
    +                            int            special_fd);
    
    154
    +static void processSpecialFd(CapIOManager *iomgr,
    
    155
    +                             StgAsyncIOOp *aiop, bool *interrupt);
    
    136 156
     
    
    157
    +
    
    158
    +/* Called early in RTS startup _before_ the storage manager is initialised,
    
    159
    + * so this is not allowed to allocate on the GC heap.
    
    160
    + */
    
    137 161
     void initCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    
    138 162
     {
    
    139 163
         initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
    
    ... ... @@ -145,12 +169,21 @@ void initCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    145 169
         /* Would never happen in a standalone process, but could plausibly happen
    
    146 170
          * if the RTS is used within another process that already has many open fds.
    
    147 171
          */
    
    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) {
    
    172
    +    if (iomgr->interrupt_fd_r >= (int)FD_SETSIZE ||
    
    173
    +        iomgr->interrupt_fd_w >= (int)FD_SETSIZE) {
    
    150 174
             barf("initCapabilityIOManagerSelectBis: fds out of select range");
    
    151 175
         }
    
    152 176
     #endif
    
    153 177
     
    
    178
    +    iomgr->n_special_fds = 0
    
    179
    +#if defined(HAVE_PREEMPTION)
    
    180
    +        + 1
    
    181
    +#endif
    
    182
    +#if defined(RTS_USER_SIGNALS)
    
    183
    +        + (iomgr->cap->no == 0 ? 1 : 0)
    
    184
    +#endif
    
    185
    +        ;
    
    186
    +
    
    154 187
         iomgr->rfds = stgMallocBytes(sizeof (fd_set), "IOManagerSelectBis");
    
    155 188
         iomgr->wfds = stgMallocBytes(sizeof (fd_set), "IOManagerSelectBis");
    
    156 189
     
    
    ... ... @@ -158,8 +191,62 @@ void initCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    158 191
         /* Signal handlers are shared across all caps. Only init once. */
    
    159 192
         if (iomgr->cap->no == 0) {
    
    160 193
             initUserSignals();
    
    194
    +        /* See Note [Non-racy signal handling using the self-pipe trick] */
    
    195
    +        newNonBlockingPipe(&iomgr->shared->signal_fd_r,
    
    196
    +                           &iomgr->shared->signal_fd_w);
    
    197
    +        if (iomgr->shared->signal_fd_r >= (int)FD_SETSIZE ||
    
    198
    +            iomgr->shared->signal_fd_w >= (int)FD_SETSIZE) {
    
    199
    +            barf("initCapabilityIOManagerSelectBis: fds out of select range");
    
    200
    +        }
    
    201
    +    }
    
    202
    +#endif
    
    203
    +}
    
    204
    +
    
    205
    +
    
    206
    +/* Called late in RtsStartup, once heap allocation is ready. */
    
    207
    +void startCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    
    208
    +{
    
    209
    +    /* ClosureTable allocates on the heap, so we can do it here not in init */
    
    210
    +    if (iomgr->n_special_fds > 0) {
    
    211
    +        bool ok = enlargeClosureTable(iomgr->cap, &iomgr->aiop_table,
    
    212
    +                                      iomgr->n_special_fds);
    
    213
    +        CHECK(ok);
    
    214
    +    }
    
    215
    +
    
    216
    +#if defined(HAVE_PREEMPTION)
    
    217
    +    insertSpecialFd(iomgr, InterruptFd, iomgr->interrupt_fd_r);
    
    218
    +#endif
    
    219
    +#if defined(RTS_USER_SIGNALS)
    
    220
    +    if (iomgr->cap->no == 0) {
    
    221
    +        /* See Note [Non-racy signal handling using the self-pipe trick] */
    
    222
    +        insertSpecialFd(iomgr, SignalFd, iomgr->shared->signal_fd_r);
    
    161 223
         }
    
    162 224
     #endif
    
    225
    +    ASSERT(iomgr->n_special_fds == sizeClosureTable(&iomgr->aiop_table));
    
    226
    +}
    
    227
    +
    
    228
    +
    
    229
    +/* For special fds, we add a dummy entry to the aiop_table and a
    
    230
    + * corresponding real entry to the aiop_poll_table.
    
    231
    + */
    
    232
    +static void insertSpecialFd(CapIOManager  *iomgr,
    
    233
    +                            enum SpecialFd special_fd_kind,
    
    234
    +                            int            special_fd)
    
    235
    +{
    
    236
    +    StgAsyncIOOp *aiop  = (StgAsyncIOOp *)allocate(iomgr->cap,
    
    237
    +                                                   sizeofW(StgAsyncIOOp));
    
    238
    +    SET_HDR(aiop, &stg_ASYNCIOOP_info, CCS_SYSTEM);
    
    239
    +    aiop->notify.unused = &stg_ASYNCIO_LIVE0_closure;
    
    240
    +    aiop->live          = &stg_ASYNCIO_LIVE0_closure;
    
    241
    +    aiop->capno         = iomgr->cap->no;
    
    242
    +    aiop->notify_type   = NotifyInternal;
    
    243
    +    aiop->outcome       = IOOpOutcomeInFlight;
    
    244
    +    aiop->operation     = IOOpCodeWaitRead;
    
    245
    +    aiop->fd            = special_fd;
    
    246
    +    aiop->internal      = special_fd_kind;
    
    247
    +
    
    248
    +    int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop);
    
    249
    +    aiop->index = ix;
    
    163 250
     }
    
    164 251
     
    
    165 252
     
    
    ... ... @@ -168,6 +255,8 @@ void freeCapabilityIOManagerSelectBis(CapIOManager *iomgr)
    168 255
     #if defined(RTS_USER_SIGNALS)
    
    169 256
         if (iomgr->cap->no == 0) {
    
    170 257
             finiUserSignals();
    
    258
    +        close(iomgr->shared->signal_fd_r);
    
    259
    +        close(iomgr->shared->signal_fd_w);
    
    171 260
         }
    
    172 261
     #endif
    
    173 262
         stgFree(iomgr->rfds);
    
    ... ... @@ -316,20 +405,9 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    316 405
         ASSERT(ncompletions > 0);
    
    317 406
     
    
    318 407
         bool interrupt = false;
    
    319
    -#if defined(HAVE_PREEMPTION)
    
    320
    -    /* If the interrupt_fd_r is ready, collect it */
    
    321
    -    if (FD_ISSET(iomgr->interrupt_fd_r, iomgr->rfds)) {
    
    322
    -        collectFdWakeup(iomgr->interrupt_fd_r);
    
    323
    -        ncompletions--;
    
    324
    -        interrupt = true;
    
    325
    -        debugTrace(DEBUG_iomanager, "Received interrupt in poll I/O manager");
    
    326
    -    }
    
    327
    -#endif
    
    328 408
     
    
    329 409
         int i = 0;
    
    330
    -    while (ncompletions > 0) {
    
    331
    -        ASSERT(i < sizeClosureTable(&iomgr->aiop_table));
    
    332
    -
    
    410
    +    while (ncompletions > 0 && i < sizeClosureTable(&iomgr->aiop_table)) {
    
    333 411
             StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, i);
    
    334 412
             int           fd   = aiop->fd;
    
    335 413
             enum IOOpCode op   = aiop->operation;
    
    ... ... @@ -339,11 +417,17 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    339 417
     
    
    340 418
             if (op == IOOpCodeWaitRead ? FD_ISSET(fd, iomgr->rfds)
    
    341 419
                                        : FD_ISSET(fd, iomgr->wfds)) {
    
    342
    -            aiop->outcome = IOOpOutcomeSuccess;
    
    343
    -            aiop->result  = 0;
    
    344
    -            removeFromTables(iomgr, i);
    
    345
    -            notifyIOCompletion(iomgr, aiop);
    
    346
    -            ncompletions--;
    
    420
    +            if (aiop->notify_type == NotifyInternal) {
    
    421
    +                processSpecialFd(iomgr, aiop, &interrupt);
    
    422
    +                ncompletions--;
    
    423
    +                i++;
    
    424
    +            } else {
    
    425
    +                aiop->outcome = IOOpOutcomeSuccess;
    
    426
    +                aiop->result  = 0;
    
    427
    +                removeFromTables(iomgr, i);
    
    428
    +                notifyIOCompletion(iomgr, aiop);
    
    429
    +                ncompletions--;
    
    430
    +            }
    
    347 431
             } else {
    
    348 432
                 /* You'd expect incrementing the table index to be unconditional,
    
    349 433
                  * but we don't increment the index if we did process the entry,
    
    ... ... @@ -353,10 +437,42 @@ static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
    353 437
                 i++;
    
    354 438
             }
    
    355 439
         }
    
    440
    +    ASSERT(ncompletions == 0);
    
    356 441
         return interrupt;
    
    357 442
     }
    
    358 443
     
    
    359 444
     
    
    445
    +static void processSpecialFd(CapIOManager *iomgr,
    
    446
    +                             StgAsyncIOOp *aiop, bool *interrupt)
    
    447
    +{
    
    448
    +    ASSERT(aiop->notify_type == NotifyInternal);
    
    449
    +    enum SpecialFd special_fd_kind = aiop->internal;
    
    450
    +    int fd = aiop->fd;
    
    451
    +
    
    452
    +    switch (special_fd_kind) {
    
    453
    +#if defined(HAVE_PREEMPTION)
    
    454
    +        case InterruptFd:
    
    455
    +            ASSERT(fd == iomgr->interrupt_fd_r);
    
    456
    +            collectFdWakeup(fd);
    
    457
    +            *interrupt = true;
    
    458
    +            debugTrace(DEBUG_iomanager, "Received interrupt in poll I/O manager");
    
    459
    +            break;
    
    460
    +#endif
    
    461
    +#if defined(RTS_USER_SIGNALS)
    
    462
    +        /* See Note [Non-racy signal handling using the self-pipe trick] */
    
    463
    +        case SignalFd:
    
    464
    +            ASSERT(fd == iomgr->shared->signal_fd_r);
    
    465
    +            int sig; siginfo_t info;
    
    466
    +            readSignalInfoFromPipe(&sig, &info, fd);
    
    467
    +            startSignalHandler(iomgr->cap, sig, &info);
    
    468
    +            debugTrace(DEBUG_iomanager,
    
    469
    +                       "Dispatched signal %d in I/O manager", sig);
    
    470
    +            break;
    
    471
    +#endif
    
    472
    +    }
    
    473
    +}
    
    474
    +
    
    475
    +
    
    360 476
     void pollCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    
    361 477
     {
    
    362 478
         if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
    
    ... ... @@ -397,10 +513,6 @@ void pollCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    397 513
                 reportSelectError();
    
    398 514
             }
    
    399 515
         }
    
    400
    -
    
    401
    -#if defined(RTS_USER_SIGNALS)
    
    402
    -    startPendingSignalHandlers(iomgr->cap);
    
    403
    -#endif
    
    404 516
     }
    
    405 517
     
    
    406 518
     
    
    ... ... @@ -440,8 +552,10 @@ bool awaitCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    440 552
              */
    
    441 553
             Time timeout = timeoutWaitTime(iomgr, wait, now);
    
    442 554
     
    
    443
    -        if (RTS_UNLIKELY(timeout == -1 &&
    
    444
    -                         isEmptyClosureTable(&iomgr->aiop_table))) {
    
    555
    +        int nfds    = sizeClosureTable(&iomgr->aiop_table);
    
    556
    +        int n_aiops = nfds - iomgr->n_special_fds;
    
    557
    +
    
    558
    +        if (RTS_UNLIKELY(timeout == -1 && n_aiops == 0)) {
    
    445 559
                 /* See Note [Deadlock detection without idle GC] */
    
    446 560
                 interrupt = notifyIdleGcDeadlock();
    
    447 561
                 if (interrupt) break;
    
    ... ... @@ -453,7 +567,7 @@ bool awaitCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    453 567
              * time, and if that timeout is reached then we schedule an idle GC
    
    454 568
              * (see handleIdleGcTimeout below).
    
    455 569
              */
    
    456
    -        bool any_pending_io = maxfd >= 0;
    
    570
    +        bool any_pending_io = n_aiops > 0;
    
    457 571
             bool timeout_idlegc_delay;
    
    458 572
             adjustTimeoutForIdleGc(any_pending_io, &timeout, &timeout_idlegc_delay);
    
    459 573
     #endif
    
    ... ... @@ -479,7 +593,7 @@ bool awaitCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    479 593
     
    
    480 594
             } else if (res > 0) {
    
    481 595
                 int ncompletions = res;
    
    482
    -            ASSERT(ncompletions <= sizeClosureTable(&iomgr->aiop_table)+1);
    
    596
    +            ASSERT(ncompletions <= nfds);
    
    483 597
                 interrupt = processIOCompletions(iomgr, ncompletions);
    
    484 598
                 /* We do _not_ need to check for timeouts again here. We know that
    
    485 599
                  * select() returned due to I/O being ready before the timeout. If
    
    ... ... @@ -489,20 +603,23 @@ bool awaitCompletedTimeoutsOrIOSelectBis(CapIOManager *iomgr)
    489 603
                  */
    
    490 604
     
    
    491 605
             } else if (errno == EINTR) {
    
    492
    -            /* We got interrupted by a signal. */
    
    493
    -
    
    494
    -#if defined(RTS_USER_SIGNALS)
    
    495
    -            /* Start any corresponding user signal handlers. If any, the run
    
    496
    -             * queue will become non-empty and we will drop out of the loop.
    
    497
    -             */
    
    498
    -            startPendingSignalHandlers(iomgr->cap);
    
    499
    -#endif
    
    500
    -
    
    501
    -            /* We can also be interrupted by the shutdown signal handler, which
    
    502
    -             * will set sched_state and so cause us to drop out of the loop.
    
    606
    +            /* We got interrupted by a signal.
    
    503 607
                  *
    
    504
    -             * For any other interruption (e.g. timer) we will go round the
    
    505
    -             * do-while loop again.
    
    608
    +             * Signals of note:
    
    609
    +             *  + signals with a user handler: these will be delivered to us
    
    610
    +             *    via the signal_fd_r pipe, which we handle by starting new
    
    611
    +             *    threads, which will terminate the loop (non-empty run queue).
    
    612
    +             *  + The backup ctl-c signal handler sets sched_state, and to avoid
    
    613
    +             *    races, it also calls interruptIOManagerPoll (which causes
    
    614
    +             *    interrupt_fd_r to become ready). We're in the EINTR case here
    
    615
    +             *    so we know the signal arrived while we were waiting, so it is
    
    616
    +             *    the getSchedState condition that will will terminate the loop.
    
    617
    +             *    (If the signal arrives before the call to poll then we will
    
    618
    +             *    notice interrupt_fd_r is ready and set interrupt=true, which
    
    619
    +             *    will terminate the loop).
    
    620
    +             *
    
    621
    +             * For any other interruption we will go round the do-while loop
    
    622
    +             * again.
    
    506 623
                  */
    
    507 624
     
    
    508 625
             } else if (errno == EBADF) {
    
    ... ... @@ -595,11 +712,20 @@ void interruptIOManagerSelectBis(CapIOManager *iomgr USED_IF_PREEMPTION)
    595 712
     }
    
    596 713
     
    
    597 714
     
    
    715
    +#if defined(RTS_USER_SIGNALS)
    
    716
    +static void signal_handler(int sig, siginfo_t *info, void *p STG_UNUSED)
    
    717
    +{
    
    718
    +    int signal_fd_w = getCapability(0)->iomgr->shared->signal_fd_w;
    
    719
    +    writeSignalInfoToPipe(sig, info, signal_fd_w);
    
    720
    +}
    
    721
    +
    
    722
    +
    
    598 723
     int installUserSignalHandlerSelectBis(int sig, int spi, sigset_t *mask,
    
    599 724
                                           bool nocldstop)
    
    600 725
     {
    
    601
    -    return installSignalHandler(sig, spi, mask, nocldstop, generic_handler);
    
    726
    +    return installSignalHandler(sig, spi, mask, nocldstop, signal_handler);
    
    602 727
     }
    
    728
    +#endif
    
    603 729
     
    
    604 730
     
    
    605 731
     /* Helper function to double the size of the aiop_table.
    
    ... ... @@ -647,15 +773,6 @@ static int collectFdSets(CapIOManager *iomgr)
    647 773
         FD_ZERO(iomgr->rfds);
    
    648 774
         FD_ZERO(iomgr->wfds);
    
    649 775
     
    
    650
    -#if defined(HAVE_PREEMPTION)
    
    651
    -    /* We're always interested in our interrupt fd */
    
    652
    -    {
    
    653
    -        int fd = iomgr->interrupt_fd_r;
    
    654
    -        maxfd = (fd > maxfd) ? fd : maxfd;
    
    655
    -        FD_SET(fd, iomgr->rfds);
    
    656
    -    }
    
    657
    -#endif
    
    658
    -
    
    659 776
         for (int ix = 0; ix < nentries; ix++) {
    
    660 777
             StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, ix);
    
    661 778
             int           fd   = aiop->fd;
    

  • rts/posix/SelectBis.h
    ... ... @@ -38,6 +38,7 @@
    38 38
     
    
    39 39
     void initCapabilityIOManagerSelectBis(CapIOManager *iomgr);
    
    40 40
     void freeCapabilityIOManagerSelectBis(CapIOManager *iomgr);
    
    41
    +void startCapabilityIOManagerSelectBis(CapIOManager *iomgr);
    
    41 42
     
    
    42 43
     /* Synchronous I/O and timer operations */
    
    43 44
     IOSubmitResult syncIOWaitReadySelectBis(CapIOManager *iomgr, StgTSO *tso,