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

Commits:

11 changed files:

Changes:

  • changelog.d/T27105
    1
    +section: rts
    
    2
    +issues: #27105
    
    3
    +mrs: !16023
    
    4
    +synopsis: RTS stopTicker is asynchronous, but is used relying on it being synchronous.
    
    5
    +description: {
    
    6
    +  As a result of the fix, the exported RTS APIs `stopTimer` and `startTimer`
    
    7
    +  are now no-ops and are deprecated. They were called at least by the process
    
    8
    +  and unix libraries. No replacement is needed.
    
    9
    +
    
    10
    +  They were used by libraries to temporarily block the RTS's use of the timer
    
    11
    +  signal. These functions no longer have a purpose since the RTS interval
    
    12
    +  timer no longer uses signals.
    
    13
    +}

  • rts/Capability.c
    ... ... @@ -31,6 +31,7 @@
    31 31
     #include "sm/OSMem.h"
    
    32 32
     #include "sm/BlockAlloc.h" // for countBlocks()
    
    33 33
     #include "IOManager.h"
    
    34
    +#include "Timer.h"
    
    34 35
     
    
    35 36
     #include <string.h>
    
    36 37
     
    
    ... ... @@ -448,7 +449,7 @@ moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS)
    448 449
         // as we free it. The alternative would be to protect the capabilities
    
    449 450
         // array with a lock but this seems more expensive than necessary.
    
    450 451
         // See #17289.
    
    451
    -    stopTimer();
    
    452
    +    blockTimer();
    
    452 453
     
    
    453 454
         if (to == 1) {
    
    454 455
             // THREADED_RTS must work on builds that don't have a mutable
    
    ... ... @@ -471,7 +472,7 @@ moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS)
    471 472
     
    
    472 473
         debugTrace(DEBUG_sched, "allocated %d more capabilities", to - from);
    
    473 474
     
    
    474
    -    startTimer();
    
    475
    +    unblockTimer();
    
    475 476
     #endif
    
    476 477
     }
    
    477 478
     
    

  • rts/RtsStartup.c
    ... ... @@ -415,8 +415,8 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
    415 415
         traceInitEvent(dumpIPEToEventLog);
    
    416 416
         initHeapProfiling();
    
    417 417
     
    
    418
    -    /* start the virtual timer 'subsystem'. */
    
    419
    -    startTimer();
    
    418
    +    /* start the timer (after initTimer above) */
    
    419
    +    unblockTimer();
    
    420 420
     
    
    421 421
     #if defined(RTS_USER_SIGNALS)
    
    422 422
         if (RtsFlags.MiscFlags.install_signal_handlers) {
    
    ... ... @@ -512,14 +512,12 @@ hs_exit_(bool wait_foreign)
    512 512
         }
    
    513 513
     #endif
    
    514 514
     
    
    515
    -    /* stop the ticker */
    
    516
    -    stopTimer();
    
    517
    -    /*
    
    518
    -     * it is quite important that we wait here as some timer implementations
    
    519
    -     * (e.g. pthread) may fire even after we exit, which may segfault as we've
    
    520
    -     * already freed the capabilities.
    
    515
    +    /* We rely on the guarantee that exitTimer stops the timer synchronously,
    
    516
    +     * which ensures the timer handler does not get run again after this point.
    
    517
    +     * We are about to start freeing resources used by the timer handler (like
    
    518
    +     * the capabilities, eventlog and profiling data structures).
    
    521 519
          */
    
    522
    -    exitTimer(true);
    
    520
    +    exitTimer();
    
    523 521
     
    
    524 522
         /*
    
    525 523
          * Dump the ticky counter definitions
    

  • rts/Schedule.c
    ... ... @@ -454,7 +454,7 @@ run_thread:
    454 454
             prev = setRecentActivity(ACTIVITY_YES);
    
    455 455
             if (prev == ACTIVITY_DONE_GC) {
    
    456 456
     #if !defined(PROFILING)
    
    457
    -            startTimer();
    
    457
    +            unpauseTimer();
    
    458 458
     #endif
    
    459 459
             }
    
    460 460
             break;
    
    ... ... @@ -1935,7 +1935,7 @@ delete_threads_and_gc:
    1935 1935
                 // it will get re-enabled if we run any threads after the GC.
    
    1936 1936
                 setRecentActivity(ACTIVITY_DONE_GC);
    
    1937 1937
     #if !defined(PROFILING)
    
    1938
    -            stopTimer();
    
    1938
    +            pauseTimer();
    
    1939 1939
     #endif
    
    1940 1940
                 break;
    
    1941 1941
             }
    
    ... ... @@ -2100,7 +2100,7 @@ forkProcess(HsStablePtr *entry
    2100 2100
         ACQUIRE_LOCK(&all_tasks_mutex);
    
    2101 2101
     #endif
    
    2102 2102
     
    
    2103
    -    stopTimer(); // See #4074
    
    2103
    +    blockTimer(); // See #4074
    
    2104 2104
     
    
    2105 2105
     #if defined(TRACING)
    
    2106 2106
         flushAllCapsEventsBufs(); // so that child won't inherit dirty file buffers
    
    ... ... @@ -2110,7 +2110,7 @@ forkProcess(HsStablePtr *entry
    2110 2110
     
    
    2111 2111
         if (pid) { // parent
    
    2112 2112
     
    
    2113
    -        startTimer(); // #4074
    
    2113
    +        unblockTimer(); // #4074
    
    2114 2114
     
    
    2115 2115
             RELEASE_LOCK(&sched_mutex);
    
    2116 2116
             RELEASE_LOCK(&sm_mutex);
    
    ... ... @@ -2224,8 +2224,9 @@ forkProcess(HsStablePtr *entry
    2224 2224
                 generations[g].threads = END_TSO_QUEUE;
    
    2225 2225
             }
    
    2226 2226
     
    
    2227
    -        // On Unix, all timers are reset in the child, so we need to start
    
    2228
    -        // the timer again.
    
    2227
    +        // The timer thread is not present in the child process, so we need
    
    2228
    +        // to initialise the timer again. Note that the timer is in a blocked
    
    2229
    +        // state when we re-init, and this is permitted.
    
    2229 2230
             initTimer();
    
    2230 2231
     
    
    2231 2232
             // TODO: need to trace various other things in the child
    
    ... ... @@ -2236,7 +2237,7 @@ forkProcess(HsStablePtr *entry
    2236 2237
     
    
    2237 2238
             // start timer after the IOManager is initialized
    
    2238 2239
             // (the idle GC may wake up the IOManager)
    
    2239
    -        startTimer();
    
    2240
    +        unblockTimer();
    
    2240 2241
     
    
    2241 2242
             // Install toplevel exception handlers, so interruption
    
    2242 2243
             // signal will be sent to the main thread.
    
    ... ... @@ -2307,7 +2308,7 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS)
    2307 2308
         // N.B. We must stop the interval timer while we are changing the
    
    2308 2309
         // capabilities array lest handle_tick may try to context switch
    
    2309 2310
         // an old capability. See #17289.
    
    2310
    -    stopTimer();
    
    2311
    +    blockTimer();
    
    2311 2312
     
    
    2312 2313
         stopAllCapabilities(&cap, task);
    
    2313 2314
     
    
    ... ... @@ -2394,7 +2395,7 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS)
    2394 2395
         // Notify IO manager that the number of capabilities has changed.
    
    2395 2396
         notifyIOManagerCapabilitiesChanged(&cap);
    
    2396 2397
     
    
    2397
    -    startTimer();
    
    2398
    +    unblockTimer();
    
    2398 2399
     
    
    2399 2400
         rts_unlock(cap);
    
    2400 2401
     
    

  • rts/Ticker.h
    ... ... @@ -12,9 +12,59 @@
    12 12
     
    
    13 13
     typedef void (*TickProc)(int);
    
    14 14
     
    
    15
    -void initTicker  (Time interval, TickProc handle_tick);
    
    16
    -void startTicker (void);
    
    17
    -void stopTicker  (void);
    
    18
    -void exitTicker  (bool wait);
    
    15
    +/* The ticker is initialised in a blocked state. Use unblockTicker to start. */
    
    16
    +void initTicker(Time interval, TickProc handle_tick);
    
    17
    +
    
    18
    +/* Stop and terminate the ticker. It does not need to be stopped first. */
    
    19
    +void exitTicker(void);
    
    20
    +
    
    21
    +/* Block and unblock the ticker handle_tick action.
    
    22
    + *
    
    23
    + * The blockTicker action is *synchronous*. When it returns the caller is
    
    24
    + * guaranteed that the tick action is blocked. The unblockTicker may be
    
    25
    + * asynchronous.
    
    26
    + *
    
    27
    + * These should be used for the purpose of *concurrency safety*: to prevent
    
    28
    + * the tick action from running concurrently with some other critical section.
    
    29
    + *
    
    30
    + * The blockTicker action is moderately expensive (because it is synchronous)
    
    31
    + * and the implementation is optimised on the assumption that this action is
    
    32
    + * infrequent (e.g. compared to tick frequency).
    
    33
    + *
    
    34
    + * It is *not* safe to call these functions from within the tick handler itself.
    
    35
    + *
    
    36
    + * It is safe to use these functions concurrently from multiple threads. They
    
    37
    + * are *not* idempotent however: each thread must pair up each blockTicker call
    
    38
    + * with exactly one corresponding unblockTicker. Additionally, initTicker acts
    
    39
    + * like blockTicker and also must be matched by a corresponding unblockTicker.
    
    40
    + */
    
    41
    +void blockTicker(void);
    
    42
    +void unblockTicker(void);
    
    43
    +
    
    44
    +/* Pause and unpause (resume) the ticker.
    
    45
    + *
    
    46
    + * The pauseTicker and unpauseTicker actions are *asynchronous*. After calling
    
    47
    + * pauseTicker, the ticker will pause eventually, but there may be another tick
    
    48
    + * action before it does pause (and theoretically there could be several but
    
    49
    + * in practice this is unlikely). Similarly, after calling unpauseTicker the
    
    50
    + * ticker will start up again eventually, but there is an unspecified delay
    
    51
    + * between the unpause and the next tick action (but in practice it is short).
    
    52
    + *
    
    53
    + * This should be used for the purpose of *efficiency*: to avoid unnecessary
    
    54
    + * OS thread wakeups caused by the ticker.
    
    55
    + *
    
    56
    + * The pairing of unpauseTicker and the handle_tick action form a
    
    57
    + * synchonises-with relation: values written before unpauseTicker can be
    
    58
    + * read from the resulting handle_tick action.
    
    59
    + *
    
    60
    + * It *is* safe to call these functions from within the tick handler itself.
    
    61
    + *
    
    62
    + * It is safe to use these functions concurrently from multiple threads, but
    
    63
    + * note that they *are* idempotent. This means it is not appropriate to use
    
    64
    + * paired pause/unpause calls concurrently. They can be used by threads based
    
    65
    + * on consistent use of some shared state or observation.
    
    66
    + */
    
    67
    +void pauseTicker(void);
    
    68
    +void unpauseTicker(void);
    
    19 69
     
    
    20 70
     #include "EndPrivate.h"

  • rts/Timer.c
    ... ... @@ -33,15 +33,6 @@
    33 33
     #define HAVE_PREEMPTION
    
    34 34
     #endif
    
    35 35
     
    
    36
    -// This global counter is used to allow multiple threads to stop the
    
    37
    -// timer temporarily with a stopTimer()/startTimer() pair.  If
    
    38
    -//      timer_enabled  == 0          timer is enabled
    
    39
    -//      timer_disabled == N, N > 0   timer is disabled by N threads
    
    40
    -// When timer_enabled makes a transition to 0, we enable the timer,
    
    41
    -// and when it makes a transition to non-0 we disable it.
    
    42
    -
    
    43
    -static StgWord timer_disabled;
    
    44
    -
    
    45 36
     /* ticks left before next pre-emptive context switch */
    
    46 37
     static int ticks_to_ctxt_switch = 0;
    
    47 38
     
    
    ... ... @@ -112,9 +103,9 @@ static
    112 103
     void
    
    113 104
     handle_tick(int unused STG_UNUSED)
    
    114 105
     {
    
    115
    -  handleProfTick();
    
    116
    -  if (RtsFlags.ConcFlags.ctxtSwitchTicks > 0
    
    117
    -      && SEQ_CST_LOAD_ALWAYS(&timer_disabled) == 0)
    
    106
    +  handleProfTick(); // Bad or worse: see issue #27250.
    
    107
    +
    
    108
    +  if (RtsFlags.ConcFlags.ctxtSwitchTicks > 0)
    
    118 109
       {
    
    119 110
           ticks_to_ctxt_switch--;
    
    120 111
           if (ticks_to_ctxt_switch <= 0) {
    
    ... ... @@ -128,7 +119,7 @@ handle_tick(int unused STG_UNUSED)
    128 119
           ticks_to_eventlog_flush--;
    
    129 120
           if (ticks_to_eventlog_flush <= 0) {
    
    130 121
               ticks_to_eventlog_flush = RtsFlags.TraceFlags.eventlogFlushTicks;
    
    131
    -          flushEventLog(NULL);
    
    122
    +          flushEventLog(NULL);  // Bad or worse: see issue #27250.
    
    132 123
           }
    
    133 124
       }
    
    134 125
     #endif
    
    ... ... @@ -153,7 +144,7 @@ handle_tick(int unused STG_UNUSED)
    153 144
                                          RtsFlags.MiscFlags.tickInterval;
    
    154 145
     #if defined(THREADED_RTS)
    
    155 146
                   wakeUpRts();
    
    156
    -              // The scheduler will call stopTimer() when it has done
    
    147
    +              // The scheduler will call pauseTimer() when it has done
    
    157 148
                   // the GC.
    
    158 149
     #endif
    
    159 150
               } else {
    
    ... ... @@ -165,10 +156,10 @@ handle_tick(int unused STG_UNUSED)
    165 156
     #if defined(PROFILING)
    
    166 157
                   if (!(RtsFlags.ProfFlags.doHeapProfile
    
    167 158
                         || RtsFlags.CcFlags.doCostCentres)) {
    
    168
    -                  stopTimer();
    
    159
    +                  pauseTimer();
    
    169 160
                   }
    
    170 161
     #else
    
    171
    -              stopTimer();
    
    162
    +              pauseTimer();
    
    172 163
     #endif
    
    173 164
               }
    
    174 165
           } else {
    
    ... ... @@ -181,48 +172,71 @@ handle_tick(int unused STG_UNUSED)
    181 172
       }
    
    182 173
     }
    
    183 174
     
    
    184
    -void
    
    185
    -initTimer(void)
    
    175
    +void initTimer(void)
    
    186 176
     {
    
    187 177
     #if defined(HAVE_PREEMPTION)
    
    188 178
         initProfTimer();
    
    189 179
         if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    190 180
             initTicker(RtsFlags.MiscFlags.tickInterval, handle_tick);
    
    191 181
         }
    
    192
    -    SEQ_CST_STORE_ALWAYS(&timer_disabled, 1);
    
    193 182
     #endif
    
    194 183
     }
    
    195 184
     
    
    196
    -void
    
    197
    -startTimer(void)
    
    185
    +/* Deprecated exported functions. Now no-ops.
    
    186
    + * Historically they were used by the process and unix libraries to disable
    
    187
    + * the signal-based interval timer, since otherwise the timer signal would
    
    188
    + * keep going off in the child process and confusing everything. The interval
    
    189
    + * timer no longer uses signals, so there is no need any more for libraries to
    
    190
    + * disable the timer. Also, the timer internal API has changed.
    
    191
    + */
    
    192
    +void stopTimer(void)  { /* no-op */ }
    
    193
    +void startTimer(void) { /* no-op */ }
    
    194
    +
    
    195
    +/* We allow multiple threads to block the timer temporarily with a
    
    196
    + * blockTimer()/unblockTimer() pair. The counting for this is done by
    
    197
    + * the ticker implementation when using blockTicker()/unblockTicker().
    
    198
    + */
    
    199
    +void unblockTimer(void)
    
    198 200
     {
    
    199 201
     #if defined(HAVE_PREEMPTION)
    
    200
    -    if (SEQ_CST_SUB_ALWAYS(&timer_disabled, 1) == 0) {
    
    201
    -        if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    202
    -            startTicker();
    
    203
    -        }
    
    202
    +    if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    203
    +        unblockTicker();
    
    204 204
         }
    
    205 205
     #endif
    
    206 206
     }
    
    207 207
     
    
    208
    -void
    
    209
    -stopTimer(void)
    
    208
    +void blockTimer(void)
    
    210 209
     {
    
    211 210
     #if defined(HAVE_PREEMPTION)
    
    212
    -    if (SEQ_CST_ADD_ALWAYS(&timer_disabled, 1) == 1) {
    
    213
    -        if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    214
    -            stopTicker();
    
    215
    -        }
    
    211
    +    if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    212
    +        blockTicker();
    
    216 213
         }
    
    217 214
     #endif
    
    218 215
     }
    
    219 216
     
    
    220
    -void
    
    221
    -exitTimer (bool wait)
    
    217
    +void pauseTimer(void)
    
    218
    +{
    
    219
    +#if defined(HAVE_PREEMPTION)
    
    220
    +    if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    221
    +        pauseTicker();
    
    222
    +    }
    
    223
    +#endif
    
    224
    +}
    
    225
    +
    
    226
    +void unpauseTimer(void)
    
    227
    +{
    
    228
    +#if defined(HAVE_PREEMPTION)
    
    229
    +    if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    230
    +        unpauseTicker();
    
    231
    +    }
    
    232
    +#endif
    
    233
    +}
    
    234
    +
    
    235
    +void exitTimer (void)
    
    222 236
     {
    
    223 237
     #if defined(HAVE_PREEMPTION)
    
    224 238
         if (RtsFlags.MiscFlags.tickInterval != 0) {
    
    225
    -        exitTicker(wait);
    
    239
    +        exitTicker();
    
    226 240
         }
    
    227 241
     #endif
    
    228 242
     }

  • rts/Timer.h
    ... ... @@ -8,5 +8,15 @@
    8 8
     
    
    9 9
     #pragma once
    
    10 10
     
    
    11
    -RTS_PRIVATE void initTimer (void);
    
    12
    -RTS_PRIVATE void exitTimer (bool wait);
    11
    +#include "BeginPrivate.h"
    
    12
    +
    
    13
    +void initTimer(void);
    
    14
    +void exitTimer(void);
    
    15
    +
    
    16
    +void blockTimer(void);
    
    17
    +void unblockTimer(void);
    
    18
    +
    
    19
    +void pauseTimer(void);
    
    20
    +void unpauseTimer(void);
    
    21
    +
    
    22
    +#include "EndPrivate.h"

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

  • rts/include/stg/SMP.h
    ... ... @@ -29,6 +29,8 @@ void arm_atomic_spin_unlock(void);
    29 29
     // Acquire/release atomic operations
    
    30 30
     #define ACQUIRE_LOAD_ALWAYS(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE)
    
    31 31
     #define RELEASE_STORE_ALWAYS(ptr,val) __atomic_store_n(ptr, val, __ATOMIC_RELEASE)
    
    32
    +#define RELEASE_ADD_ALWAYS(ptr,val) __atomic_add_fetch(ptr, val, __ATOMIC_RELEASE)
    
    33
    +#define RELEASE_SUB_ALWAYS(ptr,val) __atomic_sub_fetch(ptr, val, __ATOMIC_RELEASE)
    
    32 34
     
    
    33 35
     // Sequentially consistent atomic operations
    
    34 36
     #define SEQ_CST_LOAD_ALWAYS(ptr) __atomic_load_n(ptr, __ATOMIC_SEQ_CST)
    

  • rts/posix/Ticker.c
    ... ... @@ -103,120 +103,212 @@
    103 103
     #include <unistd.h>
    
    104 104
     #include <fcntl.h>
    
    105 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;
    
    106
    +static Time ticker_interval = DEFAULT_TICK_INTERVAL;
    
    107
    +
    
    108
    +// Atomic variable used by client threads to communicate their request to the
    
    109
    +// ticker thread to block the ticks.
    
    110
    +static int block_request_count;
    
    111
    +
    
    112
    +// Condition, mutex and cond var to communicate confirmation that the ticker is
    
    113
    +// indeed blocked.
    
    114
    +static bool block_confirmed; // must hold the mutex to get/set
    
    115
    +static Mutex block_confirmed_mutex;
    
    116
    +static Condition block_confirmed_cond;
    
    117
    +
    
    118
    +// Atomic variable used by client threads to communicate that they want the
    
    119
    +// ticker thread to pause. This communication is one-way, with no
    
    120
    +// acknowledgement.
    
    121
    +static bool pause_request;
    
    122
    +
    
    123
    +// Atomic variable used by other threads to communicate that they want the
    
    124
    +// ticker thread to exit.
    
    125
    +static bool exit_request;
    
    126
    +// Used to wait for the ticker thread to terminate after asking it to exit.
    
    127
    +static OSThreadId ticker_thread_id;
    
    128
    +
    
    129
    +// Fds used with sendFdWakeup to notify the ticker thread that any of the
    
    130
    +// *_request variables above have been set.
    
    131
    +static int notifyfd_r = -1, notifyfd_w = -1;
    
    132
    +
    
    133
    +
    
    134
    +// Synchronous, request and confirm. Not idempotent.
    
    135
    +// Request the ticker to stop ticking, and wait until it confirms
    
    136
    +// this. This guarantees no more ticks after this returns.
    
    137
    +void blockTicker(void)
    
    138
    +{
    
    139
    +    // Request
    
    140
    +    // atomic increment with release memory order
    
    141
    +    RELEASE_ADD_ALWAYS(&block_request_count, 1);
    
    142
    +
    
    143
    +    OS_ACQUIRE_LOCK(&block_confirmed_mutex);
    
    144
    +    if (!block_confirmed) {
    
    145
    +        // Avoid notifying if it's not necessary. This always happens during
    
    146
    +        // rts startup, since initTicker starts in the blocked state and then
    
    147
    +        // moreCapabilities() uses block/unblockTicker.
    
    148
    +        sendFdWakeup(notifyfd_w);
    
    149
    +    }
    
    150
    +    // Wait for confirmation
    
    151
    +    while (!block_confirmed) {
    
    152
    +        waitCondition(&block_confirmed_cond, &block_confirmed_mutex);
    
    153
    +    }
    
    154
    +    OS_RELEASE_LOCK(&block_confirmed_mutex);
    
    155
    +}
    
    115 156
     
    
    116
    -// Signaled when we want to (re)start the timer
    
    117
    -static Condition start_cond;
    
    118
    -static Mutex mutex;
    
    119
    -static OSThreadId thread;
    
    157
    +// Asynchronous request. Not idempotent.
    
    158
    +void unblockTicker(void)
    
    159
    +{
    
    160
    +    // Request
    
    161
    +    RELEASE_SUB_ALWAYS(&block_request_count, 1);
    
    162
    +    sendFdWakeup(notifyfd_w);
    
    163
    +}
    
    120 164
     
    
    121
    -// fds for interrupting the ticker
    
    122
    -static int interruptfd_r = -1, interruptfd_w = -1;
    
    165
    +// Asynchronous request. Idempotent.
    
    166
    +void pauseTicker(void)
    
    167
    +{
    
    168
    +    RELEASE_STORE_ALWAYS(&pause_request, true);
    
    169
    +    sendFdWakeup(notifyfd_w);
    
    170
    +}
    
    123 171
     
    
    124
    -static void *itimer_thread_func(void *_handle_tick)
    
    172
    +// Asynchronous request. Idempotent.
    
    173
    +void unpauseTicker(void)
    
    125 174
     {
    
    126
    -    TickProc handle_tick = _handle_tick;
    
    175
    +    RELEASE_STORE_ALWAYS(&pause_request, false);
    
    176
    +    sendFdWakeup(notifyfd_w);
    
    177
    +}
    
    127 178
     
    
    128
    -#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    129
    -    struct pollfd pollfds[1];
    
    179
    +// Synchronous. Not idempotent.
    
    180
    +// The ticker is guaranteed stopped after this.
    
    181
    +void exitTicker(void)
    
    182
    +{
    
    183
    +    ASSERT(!RELAXED_LOAD_ALWAYS(&exit_request));
    
    184
    +    RELEASE_STORE_ALWAYS(&exit_request, true);
    
    185
    +    sendFdWakeup(notifyfd_w);
    
    130 186
     
    
    131
    -    pollfds[0].fd = interruptfd_r;
    
    132
    -    pollfds[0].events = POLLIN;
    
    187
    +    // wait for ticker thread to terminate
    
    188
    +    if (pthread_join(ticker_thread_id, NULL)) {
    
    189
    +        sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
    
    190
    +    }
    
    191
    +    closeFdWakeup(notifyfd_r, notifyfd_w);
    
    192
    +    closeMutex(&block_confirmed_mutex);
    
    193
    +    closeCondition(&block_confirmed_cond);
    
    194
    +}
    
    133 195
     
    
    134
    -    struct timespec ts = { .tv_sec  = TimeToSeconds(itimer_interval)
    
    135
    -                         , .tv_nsec = TimeToNS(itimer_interval) % 1000000000
    
    136
    -                         };
    
    196
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    197
    +typedef struct timespec timeout;  // for ppoll()
    
    198
    +typedef struct { struct pollfd pollfds[1]; } fdset;
    
    137 199
     #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
    -                        };
    
    200
    +typedef struct timeval timeout;   // for select()
    
    201
    +typedef struct { int fd; fd_set selectfds; } fdset; // need to stash fd
    
    148 202
     #endif
    
    149 203
     
    
    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)) {
    
    204
    +// local helpers, to hide the difference between ppoll() and select()
    
    205
    +static void poll_init_timeout(timeout *tv, Time t);
    
    206
    +static void poll_init_fdset(fdset *fds, int fd); // single fd only
    
    207
    +// These two return: >0 if fd ready, ==0 if timeout, <0 if error
    
    208
    +static int poll_no_timeout(fdset *fdset);
    
    209
    +static int poll_with_timeout(fdset *fdset, timeout *t);
    
    153 210
     
    
    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.
    
    211
    +static void *ticker_thread_func(void *_handle_tick)
    
    212
    +{
    
    213
    +    TickProc handle_tick = _handle_tick;
    
    214
    +
    
    215
    +    // Thread-local view of our state. We compare these with the corresponding
    
    216
    +    // atomic shared variables used to request state changes.
    
    217
    +    bool blocked = true; // compare to atomic shared var block_request_count
    
    218
    +    bool paused  = false; // updated from atomic shared var pause_request
    
    219
    +    bool exit    = false; // updated from atomic shared var exit_request
    
    220
    +
    
    221
    +    timeout timeout;
    
    222
    +    fdset fdset;
    
    223
    +    poll_init_timeout(&timeout, ticker_interval);
    
    224
    +    poll_init_fdset(&fdset, notifyfd_r);
    
    225
    +
    
    226
    +    while (!exit) {
    
    227
    +
    
    228
    +        int notify;
    
    229
    +        if (blocked || paused) {
    
    230
    +            notify = poll_no_timeout(&fdset);
    
    177 231
             } 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
    -            }
    
    232
    +            notify = poll_with_timeout(&fdset, &timeout);
    
    184 233
             }
    
    185 234
     
    
    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 {
    
    235
    +        if (RTS_LIKELY(notify == 0)) {
    
    236
    +            // The time expired, no state change notification.
    
    195 237
                 handle_tick(0);
    
    238
    +
    
    239
    +        } else if (notify > 0) {
    
    240
    +            // State change notification, check the request variables.
    
    241
    +
    
    242
    +            // We rely on sendFdWakeup and select/poll to provide the
    
    243
    +            // happens-before relation. So if the request variables are set
    
    244
    +            // before calling sendFdWakeup, then we should be able to reliably
    
    245
    +            // read them here afterwards.
    
    246
    +            collectFdWakeup(notifyfd_r);
    
    247
    +
    
    248
    +            paused = ACQUIRE_LOAD_ALWAYS(&pause_request);
    
    249
    +            exit   = RELAXED_LOAD_ALWAYS(&exit_request);
    
    250
    +            int block_request_count_snapshot =
    
    251
    +                     ACQUIRE_LOAD_ALWAYS(&block_request_count);
    
    252
    +
    
    253
    +            if (block_request_count_snapshot > 0 && !blocked) {
    
    254
    +                // State change: !blocked -> blocked
    
    255
    +                blocked = true; // local state
    
    256
    +
    
    257
    +                // confirm to requesting thread(s)
    
    258
    +                OS_ACQUIRE_LOCK(&block_confirmed_mutex);
    
    259
    +                block_confirmed = true;
    
    260
    +                // Must use broadcastCondition not signalCondition since there
    
    261
    +                // could be concurrent requesting threads.
    
    262
    +                broadcastCondition(&block_confirmed_cond);
    
    263
    +                OS_RELEASE_LOCK(&block_confirmed_mutex);
    
    264
    +
    
    265
    +            } else if (block_request_count_snapshot == 0 && blocked) {
    
    266
    +                // State change: blocked -> !blocked
    
    267
    +                blocked = false; // local state
    
    268
    +
    
    269
    +                OS_ACQUIRE_LOCK(&block_confirmed_mutex);
    
    270
    +                block_confirmed = false;
    
    271
    +                OS_RELEASE_LOCK(&block_confirmed_mutex);
    
    272
    +            }
    
    273
    +
    
    274
    +        } else if (errno != EINTR) {
    
    275
    +            // While the RTS attempts to mask signals, some foreign libraries
    
    276
    +            // that rely on signal delivery may unmask them. Consequently we
    
    277
    +            // may see EINTR. See #24610.
    
    278
    +            sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
    
    196 279
             }
    
    197 280
         }
    
    198 281
     
    
    199 282
         return NULL;
    
    200 283
     }
    
    201 284
     
    
    285
    +/* Initialise the ticker on startup or re-initialise the ticker after a fork().
    
    286
    + * In the fork case, the thread will not be present, but fds are inherited.
    
    287
    + *
    
    288
    + * The ticker is started in the blocked state. A single unblockTicker should
    
    289
    + * be used to unblock.
    
    290
    + */
    
    202 291
     void
    
    203 292
     initTicker (Time interval, TickProc handle_tick)
    
    204 293
     {
    
    205
    -    itimer_interval = interval;
    
    206
    -    stopped = true;
    
    207
    -    exited = false;
    
    294
    +    ticker_interval     = interval;
    
    295
    +    block_request_count = 1;
    
    296
    +    pause_request       = false;
    
    297
    +    exit_request        = false;
    
    298
    +
    
    208 299
     #if defined(HAVE_SIGNAL_H)
    
    209 300
         sigset_t mask, omask;
    
    210 301
         int sigret;
    
    211 302
     #endif
    
    212 303
         int ret;
    
    213 304
     
    
    214
    -    initCondition(&start_cond);
    
    215
    -    initMutex(&mutex);
    
    305
    +    block_confirmed = true;
    
    306
    +    initMutex(&block_confirmed_mutex);
    
    307
    +    initCondition(&block_confirmed_cond);
    
    216 308
     
    
    217 309
         /* Open the interrupt fd synchronously.
    
    218 310
          *
    
    219
    -     * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
    
    311
    +     * We used to do it in ticker_thread_func (i.e. in the timer thread) but it
    
    220 312
          * meant that some user code could run before it and get confused by the
    
    221 313
          * allocation of the timerfd.
    
    222 314
          *
    
    ... ... @@ -226,11 +318,11 @@ initTicker (Time interval, TickProc handle_tick)
    226 318
          * descriptor closed by the first call! (see #20618)
    
    227 319
          */
    
    228 320
     
    
    229
    -    if (interruptfd_r != -1) {
    
    321
    +    if (notifyfd_r != -1) {
    
    230 322
             // don't leak the old file descriptors after a fork (#25280)
    
    231
    -        closeFdWakeup(interruptfd_r, interruptfd_w);
    
    323
    +        closeFdWakeup(notifyfd_r, notifyfd_w);
    
    232 324
         }
    
    233
    -    newFdWakeup(&interruptfd_r, &interruptfd_w);
    
    325
    +    newFdWakeup(&notifyfd_r, &notifyfd_w);
    
    234 326
     
    
    235 327
         /*
    
    236 328
          * Create the thread with all blockable signals blocked, leaving signal
    
    ... ... @@ -242,7 +334,7 @@ initTicker (Time interval, TickProc handle_tick)
    242 334
         sigfillset(&mask);
    
    243 335
         sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
    
    244 336
     #endif
    
    245
    -    ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
    
    337
    +    ret = createAttachedOSThread(&ticker_thread_id, "ghc_ticker", ticker_thread_func, (void*)handle_tick);
    
    246 338
     #if defined(HAVE_SIGNAL_H)
    
    247 339
         if (sigret == 0)
    
    248 340
             pthread_sigmask(SIG_SETMASK, &omask, NULL);
    
    ... ... @@ -253,47 +345,65 @@ initTicker (Time interval, TickProc handle_tick)
    253 345
         }
    
    254 346
     }
    
    255 347
     
    
    256
    -void
    
    257
    -startTicker(void)
    
    348
    +/* Implementation of the local helpers, to hide the difference between ppoll()
    
    349
    + * and select().
    
    350
    + */
    
    351
    +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    352
    +static void poll_init_timeout(timeout *tv, Time t)
    
    258 353
     {
    
    259
    -    OS_ACQUIRE_LOCK(&mutex);
    
    260
    -    RELAXED_STORE(&stopped, false);
    
    261
    -    signalCondition(&start_cond);
    
    262
    -    OS_RELEASE_LOCK(&mutex);
    
    354
    +    tv->tv_sec  = TimeToSeconds(t);
    
    355
    +    tv->tv_nsec = TimeToNS(t) % 1000000000;
    
    263 356
     }
    
    264 357
     
    
    265
    -/* There may be at most one additional tick fired after a call to this */
    
    266
    -void
    
    267
    -stopTicker(void)
    
    358
    +static void poll_init_fdset(fdset *fds, int fd)
    
    268 359
     {
    
    269
    -    OS_ACQUIRE_LOCK(&mutex);
    
    270
    -    RELAXED_STORE(&stopped, true);
    
    271
    -    OS_RELEASE_LOCK(&mutex);
    
    360
    +    fds->pollfds[0].fd = fd;
    
    361
    +    fds->pollfds[0].events = POLLIN;
    
    272 362
     }
    
    273 363
     
    
    274
    -/* There may be at most one additional tick fired after a call to this */
    
    275
    -void
    
    276
    -exitTicker (bool wait)
    
    364
    +static int poll_no_timeout(fdset *fds)
    
    277 365
     {
    
    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
    -    }
    
    366
    +    int nfds = 1;
    
    367
    +    return ppoll(fds->pollfds, nfds, NULL, NULL);
    
    368
    +}
    
    369
    +
    
    370
    +static int poll_with_timeout(fdset *fds, timeout *ts)
    
    371
    +{
    
    372
    +    int nfds = 1;
    
    373
    +    return ppoll(fds->pollfds, nfds, ts, NULL);
    
    374
    +}
    
    375
    +
    
    376
    +#else // select()
    
    377
    +
    
    378
    +static void poll_init_timeout(timeout *tv, Time t)
    
    379
    +{
    
    380
    +    tv->tv_sec  = TimeToSeconds(t);
    
    381
    +   /* convert remainder time in nanoseconds to microseconds, rounding up: */
    
    382
    +    tv->tv_usec = ((TimeToNS(t) % 1000000000) + 999) / 1000;
    
    383
    +}
    
    384
    +
    
    385
    +static void poll_init_fdset(fdset *fds, int fd)
    
    386
    +{
    
    387
    +    FD_ZERO(&fds->selectfds);
    
    388
    +    FD_SET(fd, &fds->selectfds);
    
    389
    +    fds->fd = fd;
    
    390
    +}
    
    391
    +
    
    392
    +static int poll_no_timeout(fdset *fds)
    
    393
    +{
    
    394
    +    int nfds = fds->fd+1;
    
    395
    +    return select(nfds, &fds->selectfds, NULL, NULL, NULL);
    
    295 396
     }
    
    296 397
     
    
    398
    +static int poll_with_timeout(fdset *fds, timeout *tv)
    
    399
    +{
    
    400
    +    struct timeval tv_tmp = *tv; // copy since select may change this value.
    
    401
    +    int nfds = fds->fd+1;
    
    402
    +    return select(nfds, &fds->selectfds, NULL, NULL, &tv_tmp);
    
    403
    +}
    
    404
    +#endif
    
    405
    +
    
    406
    +/* This is obsolete, but is used in the unix package for now */
    
    297 407
     int
    
    298 408
     rtsTimerSignal(void)
    
    299 409
     {
    

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