Duncan Coutts pushed to branch wip/dcoutts/posix-ticker at Glasgow Haskell Compiler / GHC

Commits:

7 changed files:

Changes:

  • rts/include/rts/Timer.h
    ... ... @@ -15,4 +15,4 @@
    15 15
     
    
    16 16
     void startTimer (void);
    
    17 17
     void stopTimer  (void);
    
    18
    -int rtsTimerSignal (void);
    18
    +int rtsTimerSignal (void); // Deprecated: see issue #27073

  • rts/posix/FdWakeup.c
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team 2025
    
    4
    + *
    
    5
    + * Utilities for a simple fd-based cross-thread wakeup mechanism.
    
    6
    + *
    
    7
    + * This is used to provide a mechanism to wake a thread when it is blocked
    
    8
    + * waiting on fds and timeouts. The mechanism works by including the read end
    
    9
    + * fd into the set of fds the thread waits on, and when a wake up is needed,
    
    10
    + * the write end fd is used.
    
    11
    + *
    
    12
    + * This is implemented using either eventfd() or pipe().
    
    13
    + *
    
    14
    + * Linux 2.6.22+ and FreeBSD 13+ support eventfd. It is a single fd with a
    
    15
    + * 64bit counter. It uses less resources than a pipe, and is probably a tad
    
    16
    + * faster. Using write() adds to the counter, while read() reads and resets
    
    17
    + * it. This gives us event combining.
    
    18
    + *
    
    19
    + * Otherwise we use a classic unix pipe.
    
    20
    + *
    
    21
    + * -------------------------------------------------------------------------*/
    
    22
    +
    
    23
    +#include "rts/PosixSource.h"
    
    24
    +#include "Rts.h"
    
    25
    +
    
    26
    +#include "FdWakeup.h"
    
    27
    +
    
    28
    +#include <fcntl.h>
    
    29
    +#include <unistd.h>
    
    30
    +
    
    31
    +#ifdef HAVE_SYS_EVENTFD_H
    
    32
    +#include <sys/eventfd.h>
    
    33
    +#endif
    
    34
    +
    
    35
    +#if !defined(HAVE_EVENTFD) \
    
    36
    + || (defined(HAVE_EVENTFD) && !(defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK)))
    
    37
    +static void fcntl_CLOEXEC_NONBLOCK(int fd)
    
    38
    +{
    
    39
    +    int res1 = fcntl(fd, F_SETFD, FD_CLOEXEC);
    
    40
    +    int res2 = fcntl(fd, F_SETFL, O_NONBLOCK);
    
    41
    +    if (RTS_UNLIKELY(res1 < 0 || res2 < 0)) {
    
    42
    +        sysErrorBelch("newFdWakeup fcntl()");
    
    43
    +        stg_exit(EXIT_FAILURE);
    
    44
    +    }
    
    45
    +}
    
    46
    +#endif
    
    47
    +
    
    48
    +void newFdWakeup(int *wakeup_fd_r, int *wakeup_fd_w)
    
    49
    +{
    
    50
    +#if defined(HAVE_EVENTFD)
    
    51
    +    int wakeup_fd;
    
    52
    +#if defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK)
    
    53
    +    wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
    
    54
    +#else
    
    55
    +    wakeup_fd = eventfd(0, 0);
    
    56
    +    if (wakeup_fd >= 0) fcntl_CLOEXEC_NONBLOCK(wakeup_fd);
    
    57
    +#endif
    
    58
    +    if (RTS_UNLIKELY(wakeup_fd < 0)) {
    
    59
    +        sysErrorBelch("newFdWakeup eventfd()");
    
    60
    +        stg_exit(EXIT_FAILURE);
    
    61
    +    }
    
    62
    +    /* eventfd uses the same fd for each end */
    
    63
    +    *wakeup_fd_r = wakeup_fd;
    
    64
    +    *wakeup_fd_w = wakeup_fd;
    
    65
    +#else
    
    66
    +    int pipefd[2];
    
    67
    +    int res;
    
    68
    +    res = pipe(pipefd);
    
    69
    +    if (RTS_UNLIKELY(res < 0)) {
    
    70
    +        sysErrorBelch("newFdWakeup pipe");
    
    71
    +        stg_exit(EXIT_FAILURE);
    
    72
    +    }
    
    73
    +    fcntl_CLOEXEC_NONBLOCK(pipefd[0]);
    
    74
    +    fcntl_CLOEXEC_NONBLOCK(pipefd[1]);
    
    75
    +    *wakeup_fd_r = pipefd[0]; /* read end */
    
    76
    +    *wakeup_fd_w = pipefd[1]; /* write end */
    
    77
    +#endif
    
    78
    +}
    
    79
    +
    
    80
    +void closeFdWakeup(int wakeup_fd_r, int wakeup_fd_w)
    
    81
    +{
    
    82
    +#if defined(HAVE_EVENTFD)
    
    83
    +    ASSERT(wakeup_fd_r == wakeup_fd_w);
    
    84
    +    close(wakeup_fd_r);
    
    85
    +#else
    
    86
    +    ASSERT(wakeup_fd_r != wakeup_fd_w);
    
    87
    +    close(wakeup_fd_r);
    
    88
    +    close(wakeup_fd_w);
    
    89
    +#endif
    
    90
    +}
    
    91
    +
    
    92
    +/* This is safe to use from a signal handler. Using write() to a pipe
    
    93
    + * or eventfd is fine. */
    
    94
    +void sendFdWakeup(int wakeup_fd_w)
    
    95
    +{
    
    96
    +    int res;
    
    97
    +#if defined(HAVE_EVENTFD)
    
    98
    +    uint64_t val = 1;
    
    99
    +    res = write(wakeup_fd_w, &val, 8);
    
    100
    +#else
    
    101
    +    unsigned char buf = 1;
    
    102
    +    res = write(wakeup_fd_w, &buf, 1);
    
    103
    +#endif
    
    104
    +    if (RTS_UNLIKELY(res < 0)) {
    
    105
    +        /* Unlikely the pipe buffer will fill, but it would not be an error. */
    
    106
    +        if (errno == EAGAIN) return;
    
    107
    +        sysErrorBelch("sendFdWakeup write");
    
    108
    +        stg_exit(EXIT_FAILURE);
    
    109
    +    }
    
    110
    +}
    
    111
    +
    
    112
    +void collectFdWakeup(int wakeup_fd_r)
    
    113
    +{
    
    114
    +    int res;
    
    115
    +#if defined(HAVE_EVENTFD)
    
    116
    +    uint64_t buf;
    
    117
    +    /* eventfd combines events into one counter, so a single read is enough */
    
    118
    +    res = read(wakeup_fd_r, &buf, 8);
    
    119
    +#else
    
    120
    +    /* Drain the pipe buffer. Multiple wakeup notifications could
    
    121
    +     * have been sent before we have a chance to collect them.
    
    122
    +     */
    
    123
    +    uint64_t buf;
    
    124
    +    do {
    
    125
    +        res = read(wakeup_fd_r, &buf, 8);
    
    126
    +    } while (res == 8);
    
    127
    +#endif
    
    128
    +    if (RTS_UNLIKELY(res < 0)) {
    
    129
    +        /* After the first pipe read, it could block */
    
    130
    +        if (errno == EAGAIN) return;
    
    131
    +        sysErrorBelch("collectFdWakeup read");
    
    132
    +        stg_exit(EXIT_FAILURE);
    
    133
    +    }
    
    134
    +}

  • rts/posix/FdWakeup.h
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team 2025
    
    4
    + *
    
    5
    + * Utilities for a simple fd-based cross-thread wakeup mechanism.
    
    6
    + *
    
    7
    + * It provides a mechanism for a thread that block on fds to add a simple
    
    8
    + * wakeup/notification feature.
    
    9
    + *
    
    10
    + * Start with newFdWakeup, and pass the fd_r to the thread that needs the
    
    11
    + * wakeup feature. The thread that needs to be woken should include the fd_r
    
    12
    + * into the set of fds that the thread waits on (e.g. using poll or similar).
    
    13
    + * If this fd becomes ready for read, the thread must call collectFdWakeup,
    
    14
    + * and when a wake up is needed, the write end fd is used. In any other thread
    
    15
    + * (or in a signal handler), call sendFdWakeup(fd_w) to (asynchronously) cause
    
    16
    + * the wakeup.
    
    17
    + *
    
    18
    + * There is no message payload. Multiple wakeups may be combined (if they're
    
    19
    + * sent multiple times before the notified thread can wake and call
    
    20
    + * collectFdWakeup).
    
    21
    + *
    
    22
    + * The implementation uses pipe() or eventfd() on supported OSs.
    
    23
    + *
    
    24
    + * Prototypes for functions in FdWakeup.c
    
    25
    + *
    
    26
    + * -------------------------------------------------------------------------*/
    
    27
    +
    
    28
    +#pragma once
    
    29
    +
    
    30
    +#include "BeginPrivate.h"
    
    31
    +
    
    32
    +void newFdWakeup(int *fd_r, int *fd_w);
    
    33
    +void closeFdWakeup(int fd_r, int fd_w);
    
    34
    +
    
    35
    +/* This is safe to use from a signal handler */
    
    36
    +void sendFdWakeup(int fd_w);
    
    37
    +void collectFdWakeup(int fd_r);
    
    38
    +
    
    39
    +#include "EndPrivate.h"
    
    40
    +

  • rts/posix/Ticker.c
    1 1
     /* -----------------------------------------------------------------------------
    
    2 2
      *
    
    3
    - * (c) The GHC Team, 1995-2007
    
    3
    + * (c) The GHC Team, 1995-2026
    
    4 4
      *
    
    5
    - * Posix implementation(s) of the interval timer for profiling and pre-emptive
    
    6
    - * scheduling.
    
    5
    + * The posix implementation of the interval timer, used for pre-emptive
    
    6
    + * scheduling of Haskell threads, and for sample based profiling.
    
    7
    + *
    
    8
    + * This file defines the "ticker": the platform-specific service to install and
    
    9
    + * run the timer. See rts/Timer.c for the platform-dependent view of interval
    
    10
    + * timing.
    
    7 11
      *
    
    8 12
      * ---------------------------------------------------------------------------*/
    
    9 13
     
    
    10
    -/* The interval timer is used for profiling and for context switching.
    
    11
    - * This file defines the platform-specific services to install and run the
    
    12
    - * timers, and we call this the ticker. See rts/Timer.c for the
    
    13
    - * platform-dependent view of interval timing.
    
    14
    +/* This implementation uses a posix thread which repeatedly blocks on a timeout
    
    15
    + * using either the ppoll() or select() API. This lets it also block on a file
    
    16
    + * descriptor for early wakeup.
    
    17
    + *
    
    18
    + * The design uses a simple relative time delay with no catchup. That is, time
    
    19
    + * spent by the ticker thread itself (e.g. flushing eventlog buffers) is not
    
    20
    + * accounted for, and the next tick is delayed by that much (modulo wakeup
    
    21
    + * jitter). This is probably the right thing to do: generally in realtime
    
    22
    + * systems one does not want to try to catch up when behind, since that tends
    
    23
    + * towards oversubscribing resources. Graceful degredation is usually
    
    24
    + * preferable.
    
    25
    + *
    
    26
    + * Experimental results (on Linux 6.18 on x86-64) to measure the typical
    
    27
    + * difference between the requested wakeup time and actual wakeup time for
    
    28
    + * different delay intervals:
    
    29
    + *
    
    30
    + *  interval   typical actual wakeup time after due time
    
    31
    + *   10000us   340 -- 400us      (this is the default interval)
    
    32
    + *    1000us    55 -- 100us
    
    33
    + *     100us    55us
    
    34
    + *      10us    55us
    
    35
    + *
    
    36
    + * While there's quite a bit of variance to these numbers, the results do not
    
    37
    + * vary significantly between using select, ppoll or nanosleep.
    
    38
    + *
    
    39
    + * On Linux at least, for longer delays the kernel allows itself lower wakeup
    
    40
    + * accuracy (which allows it to save power by coalescing multiple wakeups).
    
    41
    + * Similarly, the reason for 55us on the low end is that the default thread
    
    42
    + * timer slack on Linux is 50us, and context switch time accounts for the
    
    43
    + * remainder.
    
    44
    + *
    
    45
    + * In conclusion, on Linux at least, the accuracy is fine, both for the
    
    46
    + * default interval (10ms, 10000us) and for shorter intervals used during
    
    47
    + * profiling.
    
    14 48
      *
    
    15 49
      * Historically we had ticker implementations using signals. This was always a
    
    16
    - * rather shakey thing to do but we had few alternatives.
    
    50
    + * rather shakey thing to do but we originally had few alternatives.
    
    17 51
      * - One problem with using signals is that there are severe limits on what
    
    18 52
      *   code can be called from signal handlers. In particular it's not possible
    
    19 53
      *   to take locks in a signal handler contex. This was enough for contex
    
    ... ... @@ -23,17 +57,245 @@
    23 57
      *   calls (#10840) or can be overwritten by user code.
    
    24 58
      */
    
    25 59
     
    
    26
    -/* Select a ticker implementation to use:
    
    27
    - *
    
    28
    - * On modern Linux, FreeBSD and NetBSD we can use timerfd_create and a thread
    
    29
    - * that waits on it using poll. Linux has had timerfd since version 2.6.25.
    
    30
    - * NetBSD has had timerfd since version 10, and FreeBSD since version 15.
    
    31
    - *
    
    32
    - * For older version of linux/bsd without timerfd, and for all other posix
    
    33
    - * platforms, we use the implementation using posix pthreads and nanosleep().
    
    60
    +#include "rts/PosixSource.h"
    
    61
    +#include "Rts.h"
    
    62
    +
    
    63
    +#include "Ticker.h"
    
    64
    +#include "RtsUtils.h"
    
    65
    +#include "Proftimer.h"
    
    66
    +#include "Schedule.h"
    
    67
    +#include "posix/Clock.h"
    
    68
    +#include "posix/FdWakeup.h"
    
    69
    +
    
    70
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    71
    +/* We prefer the ppoll() function if available since it allows sanely waiting
    
    72
    + * on a single fd with precise timeouts (nanosecond precision). It is not in
    
    73
    + * the posix standard however and some platforms (notably glibc and freebsd)
    
    74
    + * need special CPP defines to make it available:
    
    75
    + */
    
    76
    +#define _GNU_SOURCE 1
    
    77
    +#define __BSD_VISIBLE 1
    
    78
    +#include <signal.h>
    
    79
    +#include <poll.h>
    
    80
    +#else
    
    81
    +/* Otherwise we use the classic select(), which does have microsecond
    
    82
    + * precision, but requires we build three whole 1024 bit (128 byte) fd sets
    
    83
    + * just to wait on one fd.
    
    34 84
      */
    
    35
    -#if defined(HAVE_SYS_TIMERFD_H)
    
    36
    -#include "ticker/TimerFd.c"
    
    85
    +#include <sys/select.h>
    
    86
    +#endif
    
    87
    +
    
    88
    +#include <time.h>
    
    89
    +#if HAVE_SYS_TIME_H
    
    90
    +# include <sys/time.h>
    
    91
    +#endif
    
    92
    +
    
    93
    +#if defined(HAVE_SIGNAL_H)
    
    94
    +# include <signal.h>
    
    95
    +#endif
    
    96
    +
    
    97
    +#include <string.h>
    
    98
    +
    
    99
    +#include <pthread.h>
    
    100
    +#if defined(HAVE_PTHREAD_NP_H)
    
    101
    +#include <pthread_np.h>
    
    102
    +#endif
    
    103
    +#include <unistd.h>
    
    104
    +#include <fcntl.h>
    
    105
    +
    
    106
    +static Time itimer_interval = DEFAULT_TICK_INTERVAL;
    
    107
    +
    
    108
    +// Should we be firing ticks?
    
    109
    +// Writers to this must hold the mutex below.
    
    110
    +static bool stopped = false;
    
    111
    +
    
    112
    +// should the ticker thread exit?
    
    113
    +// This can be set without holding the mutex.
    
    114
    +static bool exited = true;
    
    115
    +
    
    116
    +// Signaled when we want to (re)start the timer
    
    117
    +static Condition start_cond;
    
    118
    +static Mutex mutex;
    
    119
    +static OSThreadId thread;
    
    120
    +
    
    121
    +// fds for interrupting the ticker
    
    122
    +static int interruptfd_r = -1, interruptfd_w = -1;
    
    123
    +
    
    124
    +static void *itimer_thread_func(void *_handle_tick)
    
    125
    +{
    
    126
    +    TickProc handle_tick = _handle_tick;
    
    127
    +
    
    128
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    129
    +    struct pollfd pollfds[1];
    
    130
    +
    
    131
    +    pollfds[0].fd = interruptfd_r;
    
    132
    +    pollfds[0].events = POLLIN;
    
    133
    +
    
    134
    +    struct timespec ts = { .tv_sec  = TimeToSeconds(itimer_interval)
    
    135
    +                         , .tv_nsec = TimeToNS(itimer_interval) % 1000000000
    
    136
    +                         };
    
    37 137
     #else
    
    38
    -#include "ticker/Pthread.c"
    
    138
    +    fd_set selectfds;
    
    139
    +    FD_ZERO(&selectfds);
    
    140
    +    FD_SET(interruptfd_r, &selectfds);
    
    141
    +
    
    142
    +    struct timeval tv = { .tv_sec  = TimeToSeconds(itimer_interval)
    
    143
    +                                     /* convert remainder time in nanoseconds
    
    144
    +                                        to microseconds, rounding up: */
    
    145
    +                        , .tv_usec = ((TimeToNS(itimer_interval) % 1000000000)
    
    146
    +                                     + 999) / 1000
    
    147
    +                        };
    
    148
    +#endif
    
    149
    +
    
    150
    +    // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
    
    151
    +    // see it next time.
    
    152
    +    while (!RELAXED_LOAD_ALWAYS(&exited)) {
    
    153
    +
    
    154
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    155
    +        int nfds   = 1;
    
    156
    +        int nready = ppoll(pollfds, nfds, &ts, NULL);
    
    157
    +#else
    
    158
    +        struct timeval tv_tmp = tv; // copy since select may change this value.
    
    159
    +        int nfds   = interruptfd_r+1;
    
    160
    +        int nready = select(nfds, &selectfds, NULL, NULL, &tv_tmp);
    
    161
    +#endif
    
    162
    +        // In either case (ppoll or select), the result nready is the number
    
    163
    +        // of fds that are ready.
    
    164
    +        if (RTS_LIKELY(nready == 0)) {
    
    165
    +            // Timer expired, not interrupted, continue.
    
    166
    +        } else if (nready > 0) {
    
    167
    +            // We only monitor one fd (the interruptfd_r), so we know
    
    168
    +            // it is that fd that is ready without any further checks.
    
    169
    +            collectFdWakeup(interruptfd_r);
    
    170
    +            // No further action needed, continue on to handling the final tick
    
    171
    +            // and then stop.
    
    172
    +
    
    173
    +            // Note that we rely on sendFdWakeup and select/poll to provide the
    
    174
    +            // happens-before relation. So if 'exited' was set before calling
    
    175
    +            // sendFdWakeup, then we should be able to reliably read it after.
    
    176
    +            // And thus reading 'exited' in the while loop guard is ok.
    
    177
    +        } else {
    
    178
    +            // While the RTS attempts to mask signals, some foreign libraries
    
    179
    +            // that rely on signal delivery may unmask them. Consequently we
    
    180
    +            // may see EINTR. See #24610.
    
    181
    +            if (errno != EINTR) {
    
    182
    +                sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
    
    183
    +            }
    
    184
    +        }
    
    185
    +
    
    186
    +        // first try a cheap test
    
    187
    +        if (RELAXED_LOAD_ALWAYS(&stopped)) {
    
    188
    +            OS_ACQUIRE_LOCK(&mutex);
    
    189
    +            // should we really stop?
    
    190
    +            if (stopped) {
    
    191
    +                waitCondition(&start_cond, &mutex);
    
    192
    +            }
    
    193
    +            OS_RELEASE_LOCK(&mutex);
    
    194
    +        } else {
    
    195
    +            handle_tick(0);
    
    196
    +        }
    
    197
    +    }
    
    198
    +
    
    199
    +    return NULL;
    
    200
    +}
    
    201
    +
    
    202
    +void
    
    203
    +initTicker (Time interval, TickProc handle_tick)
    
    204
    +{
    
    205
    +    itimer_interval = interval;
    
    206
    +    stopped = true;
    
    207
    +    exited = false;
    
    208
    +#if defined(HAVE_SIGNAL_H)
    
    209
    +    sigset_t mask, omask;
    
    210
    +    int sigret;
    
    211
    +#endif
    
    212
    +    int ret;
    
    213
    +
    
    214
    +    initCondition(&start_cond);
    
    215
    +    initMutex(&mutex);
    
    216
    +
    
    217
    +    /* Open the interrupt fd synchronously.
    
    218
    +     *
    
    219
    +     * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
    
    220
    +     * meant that some user code could run before it and get confused by the
    
    221
    +     * allocation of the timerfd.
    
    222
    +     *
    
    223
    +     * See hClose002 which unsafely closes a file descriptor twice expecting an
    
    224
    +     * exception the second time: it sometimes failed when the second call to
    
    225
    +     * "close" closed our own timerfd which inadvertently reused the same file
    
    226
    +     * descriptor closed by the first call! (see #20618)
    
    227
    +     */
    
    228
    +
    
    229
    +    if (interruptfd_r != -1) {
    
    230
    +        // don't leak the old file descriptors after a fork (#25280)
    
    231
    +        closeFdWakeup(interruptfd_r, interruptfd_w);
    
    232
    +    }
    
    233
    +    newFdWakeup(&interruptfd_r, &interruptfd_w);
    
    234
    +
    
    235
    +    /*
    
    236
    +     * Create the thread with all blockable signals blocked, leaving signal
    
    237
    +     * handling to the main and/or other threads.  This is especially useful in
    
    238
    +     * the non-threaded runtime, where applications might expect sigprocmask(2)
    
    239
    +     * to effectively block signals.
    
    240
    +     */
    
    241
    +#if defined(HAVE_SIGNAL_H)
    
    242
    +    sigfillset(&mask);
    
    243
    +    sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
    
    244
    +#endif
    
    245
    +    ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
    
    246
    +#if defined(HAVE_SIGNAL_H)
    
    247
    +    if (sigret == 0)
    
    248
    +        pthread_sigmask(SIG_SETMASK, &omask, NULL);
    
    39 249
     #endif
    
    250
    +
    
    251
    +    if (ret != 0) {
    
    252
    +        barf("Ticker: Failed to spawn thread: %s", strerror(errno));
    
    253
    +    }
    
    254
    +}
    
    255
    +
    
    256
    +void
    
    257
    +startTicker(void)
    
    258
    +{
    
    259
    +    OS_ACQUIRE_LOCK(&mutex);
    
    260
    +    RELAXED_STORE(&stopped, false);
    
    261
    +    signalCondition(&start_cond);
    
    262
    +    OS_RELEASE_LOCK(&mutex);
    
    263
    +}
    
    264
    +
    
    265
    +/* There may be at most one additional tick fired after a call to this */
    
    266
    +void
    
    267
    +stopTicker(void)
    
    268
    +{
    
    269
    +    OS_ACQUIRE_LOCK(&mutex);
    
    270
    +    RELAXED_STORE(&stopped, true);
    
    271
    +    OS_RELEASE_LOCK(&mutex);
    
    272
    +}
    
    273
    +
    
    274
    +/* There may be at most one additional tick fired after a call to this */
    
    275
    +void
    
    276
    +exitTicker (bool wait)
    
    277
    +{
    
    278
    +    ASSERT(!SEQ_CST_LOAD(&exited));
    
    279
    +    SEQ_CST_STORE(&exited, true);
    
    280
    +    // ensure that ticker wakes up if stopped
    
    281
    +    startTicker();
    
    282
    +    sendFdWakeup(interruptfd_w);
    
    283
    +
    
    284
    +    // wait for ticker to terminate if necessary
    
    285
    +    if (wait) {
    
    286
    +        if (pthread_join(thread, NULL)) {
    
    287
    +            sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
    
    288
    +        }
    
    289
    +        closeFdWakeup(interruptfd_r, interruptfd_w);
    
    290
    +        closeMutex(&mutex);
    
    291
    +        closeCondition(&start_cond);
    
    292
    +    } else {
    
    293
    +        pthread_detach(thread);
    
    294
    +    }
    
    295
    +}
    
    296
    +
    
    297
    +int
    
    298
    +rtsTimerSignal(void)
    
    299
    +{
    
    300
    +    return SIGALRM;
    
    301
    +}

  • rts/posix/ticker/Pthread.c deleted
    1
    -/* -----------------------------------------------------------------------------
    
    2
    - *
    
    3
    - * (c) The GHC Team, 1995-2007
    
    4
    - *
    
    5
    - * Interval timer for profiling and pre-emptive scheduling.
    
    6
    - *
    
    7
    - * ---------------------------------------------------------------------------*/
    
    8
    -
    
    9
    -/*
    
    10
    - * We use a realtime timer by default.  I found this much more
    
    11
    - * reliable than a CPU timer:
    
    12
    - *
    
    13
    - * Experiments with different frequencies: using
    
    14
    - * CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
    
    15
    - *     1000us has  <1% impact on runtime
    
    16
    - *      100us has  ~2% impact on runtime
    
    17
    - *       10us has ~40% impact on runtime
    
    18
    - *
    
    19
    - * using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
    
    20
    - *     I cannot get it to tick faster than 10ms (10000us)
    
    21
    - *     which isn't great for profiling.
    
    22
    - *
    
    23
    - * In the threaded RTS, we can't tick in CPU time because the thread
    
    24
    - * which has the virtual timer might be idle, so the tick would never
    
    25
    - * fire.  Therefore we used to tick in realtime in the threaded RTS and
    
    26
    - * in CPU time otherwise, but now we always tick in realtime, for
    
    27
    - * several reasons:
    
    28
    - *
    
    29
    - *   - resolution (see above)
    
    30
    - *   - consistency (-threaded is the same as normal)
    
    31
    - *   - more consistency: Windows only has a realtime timer
    
    32
    - *
    
    33
    - * Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
    
    34
    - * because the latter may jump around (NTP adjustments, leap seconds
    
    35
    - * etc.).
    
    36
    - */
    
    37
    -
    
    38
    -#include "rts/PosixSource.h"
    
    39
    -#include "Rts.h"
    
    40
    -
    
    41
    -#include "Ticker.h"
    
    42
    -#include "RtsUtils.h"
    
    43
    -#include "Proftimer.h"
    
    44
    -#include "Schedule.h"
    
    45
    -#include "posix/Clock.h"
    
    46
    -#include <poll.h>
    
    47
    -
    
    48
    -#include <time.h>
    
    49
    -#if HAVE_SYS_TIME_H
    
    50
    -# include <sys/time.h>
    
    51
    -#endif
    
    52
    -
    
    53
    -#if defined(HAVE_SIGNAL_H)
    
    54
    -# include <signal.h>
    
    55
    -#endif
    
    56
    -
    
    57
    -#include <string.h>
    
    58
    -
    
    59
    -#include <pthread.h>
    
    60
    -#if defined(HAVE_PTHREAD_NP_H)
    
    61
    -#include <pthread_np.h>
    
    62
    -#endif
    
    63
    -#include <unistd.h>
    
    64
    -#include <fcntl.h>
    
    65
    -
    
    66
    -/*
    
    67
    - * TFD_CLOEXEC has been added in Linux 2.6.26.
    
    68
    - * If it is not available, we use fcntl(F_SETFD).
    
    69
    - */
    
    70
    -#if !defined(TFD_CLOEXEC)
    
    71
    -#define TFD_CLOEXEC 0
    
    72
    -#endif
    
    73
    -
    
    74
    -static Time itimer_interval = DEFAULT_TICK_INTERVAL;
    
    75
    -
    
    76
    -// Should we be firing ticks?
    
    77
    -// Writers to this must hold the mutex below.
    
    78
    -static bool stopped = false;
    
    79
    -
    
    80
    -// should the ticker thread exit?
    
    81
    -// This can be set without holding the mutex.
    
    82
    -static bool exited = true;
    
    83
    -
    
    84
    -// Signaled when we want to (re)start the timer
    
    85
    -static Condition start_cond;
    
    86
    -static Mutex mutex;
    
    87
    -static OSThreadId thread;
    
    88
    -
    
    89
    -static void *itimer_thread_func(void *_handle_tick)
    
    90
    -{
    
    91
    -    TickProc handle_tick = _handle_tick;
    
    92
    -
    
    93
    -    // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
    
    94
    -    // see it next time.
    
    95
    -    while (!RELAXED_LOAD_ALWAYS(&exited)) {
    
    96
    -        if (rtsSleep(itimer_interval) != 0) {
    
    97
    -            sysErrorBelch("Ticker: sleep failed: %s", strerror(errno));
    
    98
    -        }
    
    99
    -
    
    100
    -        // first try a cheap test
    
    101
    -        if (RELAXED_LOAD_ALWAYS(&stopped)) {
    
    102
    -            OS_ACQUIRE_LOCK(&mutex);
    
    103
    -            // should we really stop?
    
    104
    -            if (stopped) {
    
    105
    -                waitCondition(&start_cond, &mutex);
    
    106
    -            }
    
    107
    -            OS_RELEASE_LOCK(&mutex);
    
    108
    -        } else {
    
    109
    -            handle_tick(0);
    
    110
    -        }
    
    111
    -    }
    
    112
    -
    
    113
    -    return NULL;
    
    114
    -}
    
    115
    -
    
    116
    -void
    
    117
    -initTicker (Time interval, TickProc handle_tick)
    
    118
    -{
    
    119
    -    itimer_interval = interval;
    
    120
    -    stopped = true;
    
    121
    -    exited = false;
    
    122
    -#if defined(HAVE_SIGNAL_H)
    
    123
    -    sigset_t mask, omask;
    
    124
    -    int sigret;
    
    125
    -#endif
    
    126
    -    int ret;
    
    127
    -
    
    128
    -    initCondition(&start_cond);
    
    129
    -    initMutex(&mutex);
    
    130
    -
    
    131
    -    /*
    
    132
    -     * Create the thread with all blockable signals blocked, leaving signal
    
    133
    -     * handling to the main and/or other threads.  This is especially useful in
    
    134
    -     * the non-threaded runtime, where applications might expect sigprocmask(2)
    
    135
    -     * to effectively block signals.
    
    136
    -     */
    
    137
    -#if defined(HAVE_SIGNAL_H)
    
    138
    -    sigfillset(&mask);
    
    139
    -    sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
    
    140
    -#endif
    
    141
    -    ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
    
    142
    -#if defined(HAVE_SIGNAL_H)
    
    143
    -    if (sigret == 0)
    
    144
    -        pthread_sigmask(SIG_SETMASK, &omask, NULL);
    
    145
    -#endif
    
    146
    -
    
    147
    -    if (ret != 0) {
    
    148
    -        barf("Ticker: Failed to spawn thread: %s", strerror(errno));
    
    149
    -    }
    
    150
    -}
    
    151
    -
    
    152
    -void
    
    153
    -startTicker(void)
    
    154
    -{
    
    155
    -    OS_ACQUIRE_LOCK(&mutex);
    
    156
    -    RELAXED_STORE(&stopped, false);
    
    157
    -    signalCondition(&start_cond);
    
    158
    -    OS_RELEASE_LOCK(&mutex);
    
    159
    -}
    
    160
    -
    
    161
    -/* There may be at most one additional tick fired after a call to this */
    
    162
    -void
    
    163
    -stopTicker(void)
    
    164
    -{
    
    165
    -    OS_ACQUIRE_LOCK(&mutex);
    
    166
    -    RELAXED_STORE(&stopped, true);
    
    167
    -    OS_RELEASE_LOCK(&mutex);
    
    168
    -}
    
    169
    -
    
    170
    -/* There may be at most one additional tick fired after a call to this */
    
    171
    -void
    
    172
    -exitTicker (bool wait)
    
    173
    -{
    
    174
    -    ASSERT(!SEQ_CST_LOAD(&exited));
    
    175
    -    SEQ_CST_STORE(&exited, true);
    
    176
    -    // ensure that ticker wakes up if stopped
    
    177
    -    startTicker();
    
    178
    -
    
    179
    -    // wait for ticker to terminate if necessary
    
    180
    -    if (wait) {
    
    181
    -        if (pthread_join(thread, NULL)) {
    
    182
    -            sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
    
    183
    -        }
    
    184
    -        closeMutex(&mutex);
    
    185
    -        closeCondition(&start_cond);
    
    186
    -    } else {
    
    187
    -        pthread_detach(thread);
    
    188
    -    }
    
    189
    -}
    
    190
    -
    
    191
    -int
    
    192
    -rtsTimerSignal(void)
    
    193
    -{
    
    194
    -    return SIGALRM;
    
    195
    -}

  • rts/posix/ticker/TimerFd.c deleted
    1
    -/* -----------------------------------------------------------------------------
    
    2
    - *
    
    3
    - * (c) The GHC Team, 1995-2023
    
    4
    - *
    
    5
    - * Interval timer for profiling and pre-emptive scheduling.
    
    6
    - *
    
    7
    - * ---------------------------------------------------------------------------*/
    
    8
    -
    
    9
    -/*
    
    10
    - * We use a realtime timer by default.  I found this much more
    
    11
    - * reliable than a CPU timer:
    
    12
    - *
    
    13
    - * Experiments with different frequencies: using
    
    14
    - * CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
    
    15
    - *     1000us has  <1% impact on runtime
    
    16
    - *      100us has  ~2% impact on runtime
    
    17
    - *       10us has ~40% impact on runtime
    
    18
    - *
    
    19
    - * using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
    
    20
    - *     I cannot get it to tick faster than 10ms (10000us)
    
    21
    - *     which isn't great for profiling.
    
    22
    - *
    
    23
    - * In the threaded RTS, we can't tick in CPU time because the thread
    
    24
    - * which has the virtual timer might be idle, so the tick would never
    
    25
    - * fire.  Therefore we used to tick in realtime in the threaded RTS and
    
    26
    - * in CPU time otherwise, but now we always tick in realtime, for
    
    27
    - * several reasons:
    
    28
    - *
    
    29
    - *   - resolution (see above)
    
    30
    - *   - consistency (-threaded is the same as normal)
    
    31
    - *   - more consistency: Windows only has a realtime timer
    
    32
    - *
    
    33
    - * Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
    
    34
    - * because the latter may jump around (NTP adjustments, leap seconds
    
    35
    - * etc.).
    
    36
    - */
    
    37
    -
    
    38
    -#include "rts/PosixSource.h"
    
    39
    -#include "Rts.h"
    
    40
    -
    
    41
    -#include "Ticker.h"
    
    42
    -#include "RtsUtils.h"
    
    43
    -#include "Proftimer.h"
    
    44
    -#include "Schedule.h"
    
    45
    -#include "posix/Clock.h"
    
    46
    -#include <poll.h>
    
    47
    -
    
    48
    -#include <time.h>
    
    49
    -#if HAVE_SYS_TIME_H
    
    50
    -# include <sys/time.h>
    
    51
    -#endif
    
    52
    -
    
    53
    -#if defined(HAVE_SIGNAL_H)
    
    54
    -# include <signal.h>
    
    55
    -#endif
    
    56
    -
    
    57
    -#include <string.h>
    
    58
    -
    
    59
    -#include <pthread.h>
    
    60
    -#if defined(HAVE_PTHREAD_NP_H)
    
    61
    -#include <pthread_np.h>
    
    62
    -#endif
    
    63
    -#include <unistd.h>
    
    64
    -#include <fcntl.h>
    
    65
    -
    
    66
    -#include <sys/timerfd.h>
    
    67
    -
    
    68
    -
    
    69
    -/*
    
    70
    - * TFD_CLOEXEC has been added in Linux 2.6.26.
    
    71
    - * If it is not available, we use fcntl(F_SETFD).
    
    72
    - */
    
    73
    -#if !defined(TFD_CLOEXEC)
    
    74
    -#define TFD_CLOEXEC 0
    
    75
    -#endif
    
    76
    -
    
    77
    -static Time itimer_interval = DEFAULT_TICK_INTERVAL;
    
    78
    -
    
    79
    -// Should we be firing ticks?
    
    80
    -// Writers to this must hold the mutex below.
    
    81
    -static bool stopped = false;
    
    82
    -
    
    83
    -// should the ticker thread exit?
    
    84
    -// This can be set without holding the mutex.
    
    85
    -static bool exited = true;
    
    86
    -
    
    87
    -// Signaled when we want to (re)start the timer
    
    88
    -static Condition start_cond;
    
    89
    -static Mutex mutex;
    
    90
    -static OSThreadId thread;
    
    91
    -
    
    92
    -// file descriptor for the timer (Linux only)
    
    93
    -static int timerfd = -1;
    
    94
    -
    
    95
    -// pipe for signaling exit
    
    96
    -static int pipefds[2];
    
    97
    -
    
    98
    -static void *itimer_thread_func(void *_handle_tick)
    
    99
    -{
    
    100
    -    TickProc handle_tick = _handle_tick;
    
    101
    -    uint64_t nticks;
    
    102
    -    ssize_t r = 0;
    
    103
    -    struct pollfd pollfds[2];
    
    104
    -
    
    105
    -    pollfds[0].fd = pipefds[0];
    
    106
    -    pollfds[0].events = POLLIN;
    
    107
    -    pollfds[1].fd = timerfd;
    
    108
    -    pollfds[1].events = POLLIN;
    
    109
    -
    
    110
    -    // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
    
    111
    -    // see it next time.
    
    112
    -    while (!RELAXED_LOAD_ALWAYS(&exited)) {
    
    113
    -        if (poll(pollfds, 2, -1) == -1) {
    
    114
    -            // While the RTS attempts to mask signals, some foreign libraries
    
    115
    -            // may rely on signal delivery may unmask them. Consequently we may
    
    116
    -            // see EINTR. See #24610.
    
    117
    -            if (errno != EINTR) {
    
    118
    -                sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
    
    119
    -            }
    
    120
    -        }
    
    121
    -
    
    122
    -        // We check the pipe first, even though the timerfd may also have triggered.
    
    123
    -        if (pollfds[0].revents & POLLIN) {
    
    124
    -            // the pipe is ready for reading, the only possible reason is that we're exiting
    
    125
    -            exited = true; // set this again to make sure even RELAXED_LOAD will read the proper value
    
    126
    -            // no further action needed, skip ahead to handling the final tick and then stopping
    
    127
    -        }
    
    128
    -        else if (pollfds[1].revents & POLLIN) { // the timerfd is ready for reading
    
    129
    -            r = read(timerfd, &nticks, sizeof(nticks)); // this should never block now
    
    130
    -
    
    131
    -            if ((r == 0) && (errno == 0)) {
    
    132
    -               /* r == 0 is expected only for non-blocking fd (in which case
    
    133
    -                * errno should be EAGAIN) but we use a blocking fd.
    
    134
    -                *
    
    135
    -                * Due to a kernel bug (cf https://lkml.org/lkml/2019/8/16/335)
    
    136
    -                * on some platforms we could see r == 0 and errno == 0.
    
    137
    -                */
    
    138
    -               IF_DEBUG(scheduler, debugBelch("read(timerfd) returned 0 with errno=0. This is a known kernel bug. We just ignore it."));
    
    139
    -            }
    
    140
    -            else if (r != sizeof(nticks) && errno != EINTR) {
    
    141
    -               barf("Ticker: read(timerfd) failed with %s and returned %zd", strerror(errno), r);
    
    142
    -            }
    
    143
    -        }
    
    144
    -
    
    145
    -        // first try a cheap test
    
    146
    -        if (RELAXED_LOAD_ALWAYS(&stopped)) {
    
    147
    -            OS_ACQUIRE_LOCK(&mutex);
    
    148
    -            // should we really stop?
    
    149
    -            if (stopped) {
    
    150
    -                waitCondition(&start_cond, &mutex);
    
    151
    -            }
    
    152
    -            OS_RELEASE_LOCK(&mutex);
    
    153
    -        } else {
    
    154
    -            handle_tick(0);
    
    155
    -        }
    
    156
    -    }
    
    157
    -
    
    158
    -    close(timerfd);
    
    159
    -    return NULL;
    
    160
    -}
    
    161
    -
    
    162
    -void
    
    163
    -initTicker (Time interval, TickProc handle_tick)
    
    164
    -{
    
    165
    -    itimer_interval = interval;
    
    166
    -    stopped = true;
    
    167
    -    exited = false;
    
    168
    -#if defined(HAVE_SIGNAL_H)
    
    169
    -    sigset_t mask, omask;
    
    170
    -    int sigret;
    
    171
    -#endif
    
    172
    -    int ret;
    
    173
    -
    
    174
    -    initCondition(&start_cond);
    
    175
    -    initMutex(&mutex);
    
    176
    -
    
    177
    -    /* Open the file descriptor for the timer synchronously.
    
    178
    -     *
    
    179
    -     * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
    
    180
    -     * meant that some user code could run before it and get confused by the
    
    181
    -     * allocation of the timerfd.
    
    182
    -     *
    
    183
    -     * See hClose002 which unsafely closes a file descriptor twice expecting an
    
    184
    -     * exception the second time: it sometimes failed when the second call to
    
    185
    -     * "close" closed our own timerfd which inadvertently reused the same file
    
    186
    -     * descriptor closed by the first call! (see #20618)
    
    187
    -     */
    
    188
    -    struct itimerspec it;
    
    189
    -    it.it_value.tv_sec  = TimeToSeconds(itimer_interval);
    
    190
    -    it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
    
    191
    -    it.it_interval = it.it_value;
    
    192
    -
    
    193
    -    if (timerfd != -1) {
    
    194
    -        // don't leak the old file descriptors after a fork (#25280)
    
    195
    -        close(timerfd);
    
    196
    -        close(pipefds[0]);
    
    197
    -        close(pipefds[1]);
    
    198
    -        timerfd = -1;
    
    199
    -    }
    
    200
    -
    
    201
    -    timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    
    202
    -    if (timerfd == -1) {
    
    203
    -        barf("timerfd_create: %s", strerror(errno));
    
    204
    -    }
    
    205
    -    if (!TFD_CLOEXEC) {
    
    206
    -        fcntl(timerfd, F_SETFD, FD_CLOEXEC);
    
    207
    -    }
    
    208
    -    if (timerfd_settime(timerfd, 0, &it, NULL)) {
    
    209
    -        barf("timerfd_settime: %s", strerror(errno));
    
    210
    -    }
    
    211
    -
    
    212
    -    if (pipe(pipefds) < 0) {
    
    213
    -        barf("pipe: %s", strerror(errno));
    
    214
    -    }
    
    215
    -
    
    216
    -    /*
    
    217
    -     * Create the thread with all blockable signals blocked, leaving signal
    
    218
    -     * handling to the main and/or other threads.  This is especially useful in
    
    219
    -     * the non-threaded runtime, where applications might expect sigprocmask(2)
    
    220
    -     * to effectively block signals.
    
    221
    -     */
    
    222
    -#if defined(HAVE_SIGNAL_H)
    
    223
    -    sigfillset(&mask);
    
    224
    -    sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
    
    225
    -#endif
    
    226
    -    ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
    
    227
    -#if defined(HAVE_SIGNAL_H)
    
    228
    -    if (sigret == 0)
    
    229
    -        pthread_sigmask(SIG_SETMASK, &omask, NULL);
    
    230
    -#endif
    
    231
    -
    
    232
    -    if (ret != 0) {
    
    233
    -        barf("Ticker: Failed to spawn thread: %s", strerror(errno));
    
    234
    -    }
    
    235
    -}
    
    236
    -
    
    237
    -void
    
    238
    -startTicker(void)
    
    239
    -{
    
    240
    -    OS_ACQUIRE_LOCK(&mutex);
    
    241
    -    RELAXED_STORE(&stopped, false);
    
    242
    -    signalCondition(&start_cond);
    
    243
    -    OS_RELEASE_LOCK(&mutex);
    
    244
    -}
    
    245
    -
    
    246
    -/* There may be at most one additional tick fired after a call to this */
    
    247
    -void
    
    248
    -stopTicker(void)
    
    249
    -{
    
    250
    -    OS_ACQUIRE_LOCK(&mutex);
    
    251
    -    RELAXED_STORE(&stopped, true);
    
    252
    -    OS_RELEASE_LOCK(&mutex);
    
    253
    -}
    
    254
    -
    
    255
    -/* There may be at most one additional tick fired after a call to this */
    
    256
    -void
    
    257
    -exitTicker (bool wait)
    
    258
    -{
    
    259
    -    ASSERT(!SEQ_CST_LOAD(&exited));
    
    260
    -    SEQ_CST_STORE(&exited, true);
    
    261
    -    // ensure that ticker wakes up if stopped
    
    262
    -    startTicker();
    
    263
    -
    
    264
    -    // wait for ticker to terminate if necessary
    
    265
    -    if (wait) {
    
    266
    -        // write anything to the pipe to trigger poll() in the ticker thread
    
    267
    -        if (write(pipefds[1], "stop", 5) < 0) {
    
    268
    -            sysErrorBelch("Ticker: Failed to write to pipe: %s", strerror(errno));
    
    269
    -        }
    
    270
    -
    
    271
    -        if (pthread_join(thread, NULL)) {
    
    272
    -            sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
    
    273
    -        }
    
    274
    -
    
    275
    -        // These need to happen AFTER the ticker thread has finished to prevent a race condition
    
    276
    -        // where the ticker thread closes the read end of the pipe before we're done writing to it.
    
    277
    -        close(pipefds[0]);
    
    278
    -        close(pipefds[1]);
    
    279
    -
    
    280
    -        closeMutex(&mutex);
    
    281
    -        closeCondition(&start_cond);
    
    282
    -    } else {
    
    283
    -        pthread_detach(thread);
    
    284
    -    }
    
    285
    -}
    
    286
    -
    
    287
    -int
    
    288
    -rtsTimerSignal(void)
    
    289
    -{
    
    290
    -    return SIGALRM;
    
    291
    -}

  • rts/rts.cabal
    ... ... @@ -582,11 +582,9 @@ library
    582 582
                         posix/Ticker.c
    
    583 583
                         posix/OSMem.c
    
    584 584
                         posix/OSThreads.c
    
    585
    +                    posix/FdWakeup.c
    
    585 586
                         posix/Poll.c
    
    586 587
                         posix/Select.c
    
    587 588
                         posix/Signals.c
    
    588 589
                         posix/Timeout.c
    
    589 590
                         posix/TTY.c
    590
    -                    -- ticker/*.c
    
    591
    -                    -- We don't want to compile posix/ticker/*.c, these will be #included
    
    592
    -                    -- from Ticker.c