Duncan Coutts pushed to branch wip/dcoutts/signals-cleanup at Glasgow Haskell Compiler / GHC
Commits:
-
5b0be3ef
by Duncan Coutts at 2026-10-27T17:11:50+00:00
-
3f545d73
by Duncan Coutts at 2026-10-27T17:11:50+00:00
-
ddf14de2
by Duncan Coutts at 2026-10-27T17:11:50+00:00
-
d1925b83
by Duncan Coutts at 2026-10-27T17:11:50+00:00
12 changed files:
- rts/IOManager.c
- rts/IOManagerInternals.h
- rts/RtsSymbols.c
- rts/include/rts/BlockSignals.h
- rts/include/rts/storage/Closures.h
- rts/posix/IOManSignals.c
- rts/posix/IOManSignals.h
- rts/posix/Poll.c
- rts/posix/Poll.h
- rts/posix/Select.c
- rts/posix/SelectBis.c
- rts/posix/SelectBis.h
Changes:
| ... | ... | @@ -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:
|
| ... | ... | @@ -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.
|
| ... | ... | @@ -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]
|
| ... | ... | @@ -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 |
| ... | ... | @@ -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
|
| ... | ... | @@ -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];
|
| ... | ... | @@ -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"
|
| 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;
|
| ... | ... | @@ -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,
|
| ... | ... | @@ -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 */ |
| ... | ... | @@ -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;
|
| ... | ... | @@ -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,
|