Duncan Coutts pushed to branch wip/dcoutts/issue-27105-stopTicker at Glasgow Haskell Compiler / GHC
Commits:
-
cdcac142
by Duncan Coutts at 2026-05-12T22:41:16+01:00
-
9f2084b7
by Duncan Coutts at 2026-05-12T22:41:16+01:00
-
b29c4462
by Duncan Coutts at 2026-05-12T22:41:16+01:00
-
5d283c9b
by Duncan Coutts at 2026-05-12T22:41:16+01:00
11 changed files:
- + changelog.d/T27105
- rts/Capability.c
- rts/RtsStartup.c
- rts/Schedule.c
- rts/Ticker.h
- rts/Timer.c
- rts/Timer.h
- rts/include/rts/Timer.h
- rts/include/stg/SMP.h
- rts/posix/Ticker.c
- rts/win32/Ticker.c
Changes:
| 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 | +} |
| ... | ... | @@ -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 |
| ... | ... | @@ -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
|
| ... | ... | @@ -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 |
| ... | ... | @@ -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" |
| ... | ... | @@ -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 | } |
| ... | ... | @@ -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" |
| ... | ... | @@ -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 |
| ... | ... | @@ -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)
|
| ... | ... | @@ -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(¬ifyfd_r, ¬ifyfd_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 | {
|
| ... | ... | @@ -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 | } |