Duncan Coutts pushed to branch wip/dcoutts/issue-27105-stopTicker at Glasgow Haskell Compiler / GHC

Commits:

3 changed files:

Changes:

  • rts/Timer.c
    ... ... @@ -28,15 +28,6 @@
    28 28
     #include "RtsSignals.h"
    
    29 29
     #include "rts/EventLogWriter.h"
    
    30 30
     
    
    31
    -// This global counter is used to allow multiple threads to stop the
    
    32
    -// timer temporarily with a stopTimer()/startTimer() pair.  If
    
    33
    -//      timer_enabled  == 0          timer is enabled
    
    34
    -//      timer_disabled == N, N > 0   timer is disabled by N threads
    
    35
    -// When timer_enabled makes a transition to 0, we enable the timer,
    
    36
    -// and when it makes a transition to non-0 we disable it.
    
    37
    -
    
    38
    -static StgWord timer_disabled;
    
    39
    -
    
    40 31
     /* ticks left before next pre-emptive context switch */
    
    41 32
     static int ticks_to_ctxt_switch = 0;
    
    42 33
     
    
    ... ... @@ -107,9 +98,9 @@ static
    107 98
     void
    
    108 99
     handle_tick(int unused STG_UNUSED)
    
    109 100
     {
    
    110
    -  handleProfTick();
    
    111
    -  if (RtsFlags.ConcFlags.ctxtSwitchTicks > 0
    
    112
    -      && SEQ_CST_LOAD_ALWAYS(&timer_disabled) == 0)
    
    101
    +  handleProfTick(); // Bad or worse: see issue #27250.
    
    102
    +
    
    103
    +  if (RtsFlags.ConcFlags.ctxtSwitchTicks > 0)
    
    113 104
       {
    
    114 105
           ticks_to_ctxt_switch--;
    
    115 106
           if (ticks_to_ctxt_switch <= 0) {
    
    ... ... @@ -123,7 +114,7 @@ handle_tick(int unused STG_UNUSED)
    123 114
           ticks_to_eventlog_flush--;
    
    124 115
           if (ticks_to_eventlog_flush <= 0) {
    
    125 116
               ticks_to_eventlog_flush = RtsFlags.TraceFlags.eventlogFlushTicks;
    
    126
    -          flushEventLog(NULL);
    
    117
    +          flushEventLog(NULL);  // Bad or worse: see issue #27250.
    
    127 118
           }
    
    128 119
       }
    
    129 120
     #endif
    
    ... ... @@ -183,7 +174,6 @@ void initTimer(void)
    183 174
         if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    184 175
             initTicker(RtsFlags.MiscFlags.tickInterval, handle_tick);
    
    185 176
         }
    
    186
    -    SEQ_CST_STORE_ALWAYS(&timer_disabled, 1);
    
    187 177
     #endif
    
    188 178
     }
    
    189 179
     
    
    ... ... @@ -200,10 +190,8 @@ void startTimer(void) { /* no-op */ }
    200 190
     void pauseTimer(void)
    
    201 191
     {
    
    202 192
     #if defined(HAVE_PREEMPTION)
    
    203
    -    if (SEQ_CST_SUB_ALWAYS(&timer_disabled, 1) == 0) {
    
    204
    -        if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    205
    -            pauseTicker();
    
    206
    -        }
    
    193
    +    if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    194
    +        pauseTicker();
    
    207 195
         }
    
    208 196
     #endif
    
    209 197
     }
    
    ... ... @@ -211,10 +199,8 @@ void pauseTimer(void)
    211 199
     void unpauseTimer(void)
    
    212 200
     {
    
    213 201
     #if defined(HAVE_PREEMPTION)
    
    214
    -    if (SEQ_CST_ADD_ALWAYS(&timer_disabled, 1) == 1) {
    
    215
    -        if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    216
    -            unpauseTicker();
    
    217
    -        }
    
    202
    +    if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    203
    +        unpauseTicker();
    
    218 204
         }
    
    219 205
     #endif
    
    220 206
     }
    

  • rts/posix/Ticker.c
    ... ... @@ -103,120 +103,112 @@
    103 103
     #include <unistd.h>
    
    104 104
     #include <fcntl.h>
    
    105 105
     
    
    106
    -static Time itimer_interval = DEFAULT_TICK_INTERVAL;
    
    107 106
     
    
    108
    -// Should we be firing ticks?
    
    109
    -// Writers to this must hold the mutex below.
    
    110
    -static bool stopped = false;
    
    107
    +// Forward declarations of local types and helper functions to hide the
    
    108
    +// difference between ppoll() and select()
    
    109
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    110
    +typedef struct timespec timeout;  // for ppoll()
    
    111
    +typedef struct { struct pollfd pollfds[1]; } fdset;
    
    112
    +#else
    
    113
    +typedef struct timeval timeout;   // for select()
    
    114
    +typedef struct { int fd; fd_set selectfds; } fdset; // need to stash fd
    
    115
    +#endif
    
    116
    +static void poll_init_timeout(timeout *tv, Time t);
    
    117
    +static void poll_init_fdset(fdset *fds, int fd); // single fd only
    
    118
    +// poll_*_timeout returns >0 if fd ready, ==0 if timeout, <0 if error
    
    119
    +static int poll_no_timeout(fdset *fdset);
    
    120
    +static int poll_with_timeout(fdset *fdset, timeout *t);
    
    121
    +
    
    111 122
     
    
    112
    -// should the ticker thread exit?
    
    113
    -// This can be set without holding the mutex.
    
    114
    -static bool exited = true;
    
    123
    +static Time ticker_interval = DEFAULT_TICK_INTERVAL;
    
    115 124
     
    
    116
    -// Signaled when we want to (re)start the timer
    
    117
    -static Condition start_cond;
    
    118
    -static Mutex mutex;
    
    119
    -static OSThreadId thread;
    
    125
    +// Atomic variable used by client threads to communicate that they want the
    
    126
    +// ticker thread to pause. This communication is one-way, with no
    
    127
    +// acknowledgement.
    
    128
    +static bool pause_request;
    
    120 129
     
    
    121
    -// fds for interrupting the ticker
    
    122
    -static int interruptfd_r = -1, interruptfd_w = -1;
    
    130
    +// Atomic variable used by other threads to communicate that they want the
    
    131
    +// ticker thread to exit.
    
    132
    +static bool exit_request;
    
    123 133
     
    
    124
    -static void *itimer_thread_func(void *_handle_tick)
    
    134
    +// Used to wait for the ticker thread to terminate after asking it to exit.
    
    135
    +static OSThreadId ticker_thread_id;
    
    136
    +
    
    137
    +// Fds used with sendFdWakeup to notify the ticker thread that any of the
    
    138
    +// *_request variables above have been set.
    
    139
    +static int notifyfd_r = -1, notifyfd_w = -1;
    
    140
    +
    
    141
    +static void *ticker_thread_func(void *_handle_tick)
    
    125 142
     {
    
    126 143
         TickProc handle_tick = _handle_tick;
    
    127 144
     
    
    128
    -#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    129
    -    struct pollfd pollfds[1];
    
    145
    +    // Thread-local view of our state. We compare these with the corresponding
    
    146
    +    // atomic shared variables used to request state changes.
    
    147
    +    bool paused  = true;  // updated from atomic shared var pause_request
    
    148
    +    bool exit    = false; // updated from atomic shared var exit_request
    
    149
    +    // Note that we start paused.
    
    130 150
     
    
    131
    -    pollfds[0].fd = interruptfd_r;
    
    132
    -    pollfds[0].events = POLLIN;
    
    151
    +    timeout timeout;
    
    152
    +    fdset fdset;
    
    153
    +    poll_init_timeout(&timeout, ticker_interval);
    
    154
    +    poll_init_fdset(&fdset, notifyfd_r);
    
    133 155
     
    
    134
    -    struct timespec ts = { .tv_sec  = TimeToSeconds(itimer_interval)
    
    135
    -                         , .tv_nsec = TimeToNS(itimer_interval) % 1000000000
    
    136
    -                         };
    
    137
    -#else
    
    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)) {
    
    156
    +    while (!exit) {
    
    153 157
     
    
    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.
    
    158
    +        int notify;
    
    159
    +        if (paused) {
    
    160
    +            notify = poll_no_timeout(&fdset);
    
    177 161
             } 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
    -            }
    
    162
    +            notify = poll_with_timeout(&fdset, &timeout);
    
    184 163
             }
    
    185 164
     
    
    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 {
    
    165
    +        if (RTS_LIKELY(notify == 0)) {
    
    166
    +            // The time expired, no state change notification.
    
    195 167
                 handle_tick(0);
    
    168
    +
    
    169
    +        } else if (notify > 0) {
    
    170
    +            // State change notification, check the request variables.
    
    171
    +
    
    172
    +            // We rely on sendFdWakeup and select/poll to provide the
    
    173
    +            // happens-before relation. So if the request variables are set
    
    174
    +            // before calling sendFdWakeup, then we should be able to reliably
    
    175
    +            // read them here afterwards.
    
    176
    +            collectFdWakeup(notifyfd_r);
    
    177
    +
    
    178
    +            paused = ACQUIRE_LOAD_ALWAYS(&pause_request);
    
    179
    +            exit   = RELAXED_LOAD_ALWAYS(&exit_request);
    
    180
    +        } else if (errno != EINTR) {
    
    181
    +            // While the RTS attempts to mask signals, some foreign libraries
    
    182
    +            // that rely on signal delivery may unmask them. Consequently we
    
    183
    +            // may see EINTR. See #24610.
    
    184
    +            sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
    
    196 185
             }
    
    197 186
         }
    
    198 187
     
    
    199 188
         return NULL;
    
    200 189
     }
    
    201 190
     
    
    191
    +/* Initialise the ticker on startup or re-initialise the ticker after a fork().
    
    192
    + * In the fork case, the thread will not be present, but fds are inherited.
    
    193
    + *
    
    194
    + * The ticker is started in the paused state. Use unpauseTicker to continue.
    
    195
    + */
    
    202 196
     void
    
    203 197
     initTicker (Time interval, TickProc handle_tick)
    
    204 198
     {
    
    205
    -    itimer_interval = interval;
    
    206
    -    stopped = true;
    
    207
    -    exited = false;
    
    199
    +    ticker_interval     = interval;
    
    200
    +    pause_request       = true;
    
    201
    +    exit_request        = false;
    
    202
    +
    
    208 203
     #if defined(HAVE_SIGNAL_H)
    
    209 204
         sigset_t mask, omask;
    
    210 205
         int sigret;
    
    211 206
     #endif
    
    212 207
         int ret;
    
    213 208
     
    
    214
    -    initCondition(&start_cond);
    
    215
    -    initMutex(&mutex);
    
    216
    -
    
    217 209
         /* Open the interrupt fd synchronously.
    
    218 210
          *
    
    219
    -     * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
    
    211
    +     * We used to do it in ticker_thread_func (i.e. in the timer thread) but it
    
    220 212
          * meant that some user code could run before it and get confused by the
    
    221 213
          * allocation of the timerfd.
    
    222 214
          *
    
    ... ... @@ -226,11 +218,11 @@ initTicker (Time interval, TickProc handle_tick)
    226 218
          * descriptor closed by the first call! (see #20618)
    
    227 219
          */
    
    228 220
     
    
    229
    -    if (interruptfd_r != -1) {
    
    221
    +    if (notifyfd_r != -1) {
    
    230 222
             // don't leak the old file descriptors after a fork (#25280)
    
    231
    -        closeFdWakeup(interruptfd_r, interruptfd_w);
    
    223
    +        closeFdWakeup(notifyfd_r, notifyfd_w);
    
    232 224
         }
    
    233
    -    newFdWakeup(&interruptfd_r, &interruptfd_w);
    
    225
    +    newFdWakeup(&notifyfd_r, &notifyfd_w);
    
    234 226
     
    
    235 227
         /*
    
    236 228
          * Create the thread with all blockable signals blocked, leaving signal
    
    ... ... @@ -242,7 +234,7 @@ initTicker (Time interval, TickProc handle_tick)
    242 234
         sigfillset(&mask);
    
    243 235
         sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
    
    244 236
     #endif
    
    245
    -    ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
    
    237
    +    ret = createAttachedOSThread(&ticker_thread_id, "ghc_ticker", ticker_thread_func, (void*)handle_tick);
    
    246 238
     #if defined(HAVE_SIGNAL_H)
    
    247 239
         if (sigret == 0)
    
    248 240
             pthread_sigmask(SIG_SETMASK, &omask, NULL);
    
    ... ... @@ -256,10 +248,8 @@ initTicker (Time interval, TickProc handle_tick)
    256 248
     /* Asynchronous. Idempotent. */
    
    257 249
     void unpauseTicker(void)
    
    258 250
     {
    
    259
    -    OS_ACQUIRE_LOCK(&mutex);
    
    260
    -    RELAXED_STORE(&stopped, false);
    
    261
    -    signalCondition(&start_cond);
    
    262
    -    OS_RELEASE_LOCK(&mutex);
    
    251
    +    RELEASE_STORE_ALWAYS(&pause_request, false);
    
    252
    +    sendFdWakeup(notifyfd_w);
    
    263 253
     }
    
    264 254
     
    
    265 255
     /* Asynchronous. Idempotent.
    
    ... ... @@ -268,9 +258,8 @@ void unpauseTicker(void)
    268 258
      */
    
    269 259
     void pauseTicker(void)
    
    270 260
     {
    
    271
    -    OS_ACQUIRE_LOCK(&mutex);
    
    272
    -    RELAXED_STORE(&stopped, true);
    
    273
    -    OS_RELEASE_LOCK(&mutex);
    
    261
    +    RELEASE_STORE_ALWAYS(&pause_request, true);
    
    262
    +    sendFdWakeup(notifyfd_w);
    
    274 263
     }
    
    275 264
     
    
    276 265
     /* Synchronous. Not idempotent.
    
    ... ... @@ -278,21 +267,90 @@ void pauseTicker(void)
    278 267
      */
    
    279 268
     void exitTicker(void)
    
    280 269
     {
    
    281
    -    ASSERT(!SEQ_CST_LOAD(&exited));
    
    282
    -    SEQ_CST_STORE(&exited, true);
    
    283
    -    // ensure that ticker wakes up if stopped
    
    284
    -    unpauseTicker();
    
    285
    -    sendFdWakeup(interruptfd_w);
    
    286
    -
    
    287
    -    // wait for ticker to terminate
    
    288
    -    if (pthread_join(thread, NULL)) {
    
    270
    +    ASSERT(!RELAXED_LOAD_ALWAYS(&exit_request));
    
    271
    +    RELEASE_STORE_ALWAYS(&exit_request, true);
    
    272
    +    sendFdWakeup(notifyfd_w);
    
    273
    +
    
    274
    +    // wait for ticker thread to terminate
    
    275
    +    if (pthread_join(ticker_thread_id, NULL)) {
    
    289 276
             sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
    
    290 277
         }
    
    291
    -    closeFdWakeup(interruptfd_r, interruptfd_w);
    
    292
    -    closeMutex(&mutex);
    
    293
    -    closeCondition(&start_cond);
    
    278
    +    closeFdWakeup(notifyfd_r, notifyfd_w);
    
    294 279
     }
    
    295 280
     
    
    281
    +/* Implementation of the local helpers, to hide the difference between ppoll()
    
    282
    + * and select().
    
    283
    + */
    
    284
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    285
    +static void poll_init_timeout(timeout *tv, Time t)
    
    286
    +{
    
    287
    +    tv->tv_sec  = TimeToSeconds(t);
    
    288
    +    tv->tv_nsec = TimeToNS(t) % 1000000000;
    
    289
    +}
    
    290
    +
    
    291
    +static void poll_init_fdset(fdset *fds, int fd)
    
    292
    +{
    
    293
    +    fds->pollfds[0].fd = fd;
    
    294
    +    fds->pollfds[0].events = POLLIN;
    
    295
    +}
    
    296
    +
    
    297
    +static int poll_no_timeout(fdset *fds)
    
    298
    +{
    
    299
    +    int nfds = 1;
    
    300
    +    return ppoll(fds->pollfds, nfds, NULL, NULL);
    
    301
    +}
    
    302
    +
    
    303
    +static int poll_with_timeout(fdset *fds, timeout *ts)
    
    304
    +{
    
    305
    +    int nfds = 1;
    
    306
    +    return ppoll(fds->pollfds, nfds, ts, NULL);
    
    307
    +}
    
    308
    +
    
    309
    +#else // select()
    
    310
    +
    
    311
    +static void poll_init_timeout(timeout *tv, Time t)
    
    312
    +{
    
    313
    +    tv->tv_sec  = TimeToSeconds(t);
    
    314
    +    /* convert remainder time in nanoseconds to microseconds, rounding up: */
    
    315
    +    tv->tv_usec = ((TimeToNS(t) % 1000000000) + 999) / 1000;
    
    316
    +}
    
    317
    +
    
    318
    +static void poll_init_fdset(fdset *fds, int fd)
    
    319
    +{
    
    320
    +    /* select() modifies the fd_set: it uses the same fd_set for reporting as
    
    321
    +     * for input. Thus we must rebuild it every time. We can optimise this
    
    322
    +     * rebuilding somewhat however if we rely on select() not modifying the
    
    323
    +     * bits that we didn't ask it to look at. So we can zero the fd_set just
    
    324
    +     * once, and then only reset the single bit for the single fd, before each
    
    325
    +     * call to selct().
    
    326
    +     */
    
    327
    +    fds->fd = fd;
    
    328
    +    FD_ZERO(&fds->selectfds);
    
    329
    +}
    
    330
    +
    
    331
    +static int poll_no_timeout(fdset *fds)
    
    332
    +{
    
    333
    +    /* select() modifies the fd_set so we must set it every time, but we rely
    
    334
    +     * on it not touching other bits to avoid having to FD_ZERO it every time
    
    335
    +     */
    
    336
    +    FD_SET(fds->fd, &fds->selectfds);
    
    337
    +    int nfds = fds->fd+1;
    
    338
    +    return select(nfds, &fds->selectfds, NULL, NULL, NULL);
    
    339
    +}
    
    340
    +
    
    341
    +static int poll_with_timeout(fdset *fds, timeout *tv)
    
    342
    +{
    
    343
    +    struct timeval tv_tmp = *tv; // copy since select may change this value.
    
    344
    +    /* select() modifies the fd_set so we must set it every time, but we rely
    
    345
    +     * on it not touching other bits to avoid having to FD_ZERO it every time
    
    346
    +     */
    
    347
    +    FD_SET(fds->fd, &fds->selectfds);
    
    348
    +    int nfds = fds->fd+1;
    
    349
    +    return select(nfds, &fds->selectfds, NULL, NULL, &tv_tmp);
    
    350
    +}
    
    351
    +#endif
    
    352
    +
    
    353
    +/* This is obsolete, but is used in the unix package for now */
    
    296 354
     int
    
    297 355
     rtsTimerSignal(void)
    
    298 356
     {
    

  • rts/win32/Ticker.c
    ... ... @@ -11,7 +11,11 @@
    11 11
     
    
    12 12
     static TickProc tick_proc = NULL;
    
    13 13
     static HANDLE timer_queue = NULL;
    
    14
    +
    
    15
    +static Mutex lock; // To protect the timer and paused var below
    
    14 16
     static HANDLE timer       = NULL;
    
    17
    +static bool paused;
    
    18
    +
    
    15 19
     static Time tick_interval = 0;
    
    16 20
     
    
    17 21
     static VOID CALLBACK tick_callback(
    
    ... ... @@ -36,12 +40,19 @@ static VOID CALLBACK tick_callback(
    36 40
     // This seems to be the case starting at some point during the
    
    37 41
     // Windows 7 lifetime and any newer versions of windows.
    
    38 42
     
    
    43
    +// Forward decls
    
    44
    +static void startTicker(void);
    
    45
    +static void stopTicker(bool synchronous);
    
    46
    +
    
    39 47
     void
    
    40 48
     initTicker (Time interval, TickProc handle_tick)
    
    41 49
     {
    
    50
    +    ASSERT(timer_queue == NULL);
    
    42 51
         tick_interval = interval;
    
    43 52
         tick_proc = handle_tick;
    
    44 53
     
    
    54
    +    OS_INIT_LOCK(&lock);
    
    55
    +    paused = true; // starts paused
    
    45 56
         timer_queue = CreateTimerQueue();
    
    46 57
         if (timer_queue == NULL) {
    
    47 58
             sysErrorBelch("CreateTimerQueue");
    
    ... ... @@ -49,43 +60,81 @@ initTicker (Time interval, TickProc handle_tick)
    49 60
         }
    
    50 61
     }
    
    51 62
     
    
    63
    +// Asynchronous. Idempotent.
    
    52 64
     void
    
    53 65
     unpauseTicker(void)
    
    54 66
     {
    
    55
    -    BOOL r;
    
    56
    -
    
    57
    -    r = CreateTimerQueueTimer(&timer,
    
    58
    -                              timer_queue,
    
    59
    -                              tick_callback,
    
    60
    -                              0,
    
    61
    -                              0,
    
    62
    -                              TimeToMS(tick_interval), // ms
    
    63
    -                              WT_EXECUTEINTIMERTHREAD);
    
    64
    -    if (r == 0) {
    
    65
    -        sysErrorBelch("CreateTimerQueueTimer");
    
    66
    -        stg_exit(EXIT_FAILURE);
    
    67
    +    OS_ACQUIRE_LOCK(&lock);
    
    68
    +    if (paused) {
    
    69
    +        startTicker();
    
    67 70
         }
    
    71
    +    paused = false;
    
    72
    +    OS_RELEASE_LOCK(&lock);
    
    68 73
     }
    
    69 74
     
    
    75
    +// Asynchronous. Idempotent.
    
    70 76
     void
    
    71 77
     pauseTicker(void)
    
    72 78
     {
    
    73
    -    if (timer_queue != NULL && timer != NULL) {
    
    74
    -        DeleteTimerQueueTimer(timer_queue, timer, NULL);
    
    75
    -        timer = NULL;
    
    79
    +    OS_ACQUIRE_LOCK(&lock);
    
    80
    +    if (!paused) {
    
    81
    +        /* pauseTicker is called from within the handle_tick, so stopping
    
    82
    +         * the ticker here /must/ be asynchronous or we will deadlock! */
    
    83
    +        stopTicker(false /* asynchronous */);
    
    76 84
         }
    
    85
    +    paused = true;
    
    86
    +    OS_RELEASE_LOCK(&lock);
    
    77 87
     }
    
    78 88
     
    
    79 89
     void
    
    80 90
     exitTicker(void)
    
    81 91
     {
    
    82
    -    pauseTicker();
    
    83
    -    if (timer_queue != NULL) {
    
    84
    -        // From the docs for DeleteTimerQueueEx:
    
    85
    -        //   If this parameter is INVALID_HANDLE_VALUE, the function waits
    
    86
    -        //   for all callback functions to complete before returning.
    
    87
    -        HANDLE completion = INVALID_HANDLE_VALUE;
    
    88
    -        DeleteTimerQueueEx(timer_queue, completion);
    
    89
    -        timer_queue = NULL;
    
    92
    +    ASSERT(timer_queue != NULL);
    
    93
    +
    
    94
    +    OS_ACQUIRE_LOCK(&lock);
    
    95
    +    if (!paused) {
    
    96
    +        stopTicker(true /* synchronous */);
    
    97
    +    }
    
    98
    +    OS_RELEASE_LOCK(&lock);
    
    99
    +
    
    100
    +    // From the docs for DeleteTimerQueueEx:
    
    101
    +    //   If this parameter is INVALID_HANDLE_VALUE, the function waits
    
    102
    +    //   for all callback functions to complete before returning.
    
    103
    +    // This is a belt-and-braces approach to ensuring exitTicker is synchronous,
    
    104
    +    // since stopTicker(true) is already synchronous and there's only one timer.
    
    105
    +    HANDLE completion = INVALID_HANDLE_VALUE;
    
    106
    +    DeleteTimerQueueEx(timer_queue, completion);
    
    107
    +    timer_queue = NULL;
    
    108
    +}
    
    109
    +
    
    110
    +static void startTicker(void) {
    
    111
    +    ASSERT(timer_queue != NULL && timer == NULL);
    
    112
    +    DWORD interval = TimeToMS(tick_interval); // ms
    
    113
    +    BOOL r = CreateTimerQueueTimer(&timer,
    
    114
    +                                   timer_queue,
    
    115
    +                                   tick_callback,
    
    116
    +                                   NULL,     // callback param
    
    117
    +                                   interval, // inital interval
    
    118
    +                                   interval, // recurrant interval
    
    119
    +                                   WT_EXECUTEINTIMERTHREAD);
    
    120
    +    //TODO: using WT_EXECUTEINTIMERTHREAD is fine for context switching, and
    
    121
    +    // plausibly also ok for profile sampling but is way out for eventlog
    
    122
    +    // flushing. The eventlog flush does a global synchronisation of all
    
    123
    +    // capabilities and I/O! And with eventlog providers, it calls arbitrary
    
    124
    +    // user code. This is not ok! See issue #27250.
    
    125
    +    if (r == 0) {
    
    126
    +        sysErrorBelch("CreateTimerQueueTimer");
    
    127
    +        stg_exit(EXIT_FAILURE);
    
    90 128
         }
    
    129
    +    ASSERT(timer != NULL);
    
    130
    +}
    
    131
    +
    
    132
    +static void stopTicker(bool synchronous) {
    
    133
    +    ASSERT(timer_queue != NULL && timer != NULL);
    
    134
    +    // From the docs for DeleteTimerQueueTimer:
    
    135
    +    // If this parameter is INVALID_HANDLE_VALUE, the function waits for any
    
    136
    +    // running timer callback functions to complete before returning.
    
    137
    +    HANDLE completion = synchronous ? INVALID_HANDLE_VALUE : NULL;
    
    138
    +    DeleteTimerQueueTimer(timer_queue, timer, completion);
    
    139
    +    timer = NULL;
    
    91 140
     }